02/10/2018, 14:27

[Học OOP] Bài 4: Constructor, destructor và Copy constructor trong hướng đối tượng c++

Bài viết này là phần 4 trong 9 bài của Series Học lập trình hướng đối tượng OOP Học lập trình hướng đối tượng OOP [Học OOP] Bài 1: Các đặc điểm mới trong c++ [Học OOP] Bài 2: Tổng quan về lập trình hướng đối tượng [Học OOP] Bài 3: Lớp trong lập trình hướng đối tượng [Học OOP] ...

Bài viết này là phần 4 trong 9 bài của Series Học lập trình hướng đối tượng OOP

Học lập trình hướng đối tượng OOP
  • [Học OOP] Bài 1: Các đặc điểm mới trong c++
  • [Học OOP] Bài 2: Tổng quan về lập trình hướng đối tượng
  • [Học OOP] Bài 3: Lớp trong lập trình hướng đối tượng
  • [Học OOP] Bài 4: Constructor, destructor và Copy constructor trong hướng đối tượng c++
  • [Học OOP] Bài 5: Static trong hướng đối tượng c++
  • [Học OOP] Bài 6: Hàm bạn, lớp bạn trong hướng đối tượng c++
  • [Học OOP] Bài 7: Overload toán tử trong Lập trình hướng đối tượng c++
  • [Học OOP] Bài 8: Kế thừa trong lập trình hướng đối tượng c++ (Phần 1)
  • [Học OOP] Bài 9: Hiểu kế thừa như thế nào cho đúng trong hướng đối tượng (Phần 2)

Copy Constructor được dùng rất nhiều trong lập trình và đối với lập trình hướng đối tượng khi dữ liệu của đối tượng là một con trỏ thì nó lại rất cực kì quan trọng mà bạn phải chú tâm đến.

Ở bài viết trước [Học OOP] Bài 3: Lớp trong lập trình hướng đối tượng mình đã giới thiệu sơ về Constructor và Destructor. Trong bài này mình tiếp tục giới thiệu về Copy Constructor và đây là một chủ đề khá quan trọng nó sẽ giúp bạn hiểu hơn về các cơ chế hoạt động của ngôn ngữ lập trình.

1. Copy Constructor là gì?

a. Giới thiệu Copy Constructor

Copy Constructor là một Constructor đặc biệt, nó được dùng để tạo một bản sao của một đối tượng đã có trước đó. Copy Constructor có tham số là địa chỉ vùng nhớ tham chiếu đến đối tượng cần sao chép.

Copy Constructor 2 đối tượng trong lập trình hướng đối tượng

Copy Constructor 2 đối tượng trong lập trình hướng đối tượng

Trong hình minh họa trên, Ban đầu chúng ta có đối tượng A có địa chỉ vùng nhớ là 0x0100, nó là một hình tròn màu vàng viền đen. Do nhu cầu nên chúng ta muốn tạo ra một bản sao giống hệt Đối tượng A và đặt tên là Đối tượng B có địa chỉ vùng nhớ 0x0300. Vai trò copy constructor trong ví dụ trên chỉ có vậy.

b. Vì sao phải dùng Copy Constructor?

Sau khi đọc xong phần giới thiệu có lẽ bạn đặt khá nhiều câu hỏi, vai trò chỉ là sao chép thì tại sao lại nói là nó quan trọng, trong khi phép gán đã giải quyết được vấn đề sao chép?

Như bạn cũng biết, Pointer (Con trỏ) trong C++ lưu địa chỉ vùng nhớ, chính về thế, nếu bạn muốn sao chép nội dung 2 pointer trỏ đến bằng phép gán b = a thì lúc này nó chỉ gán địa chỉ vùng nhớ của a cho b mà thôi, và nội dung của 2 là một, nên khi bạn tác động lên dữ liệu mà a hoặc b trỏ đến thì cả nội dung của a và b đều sẽ thay đổi. Nên phương án gán này không hợp lý.

Trong C++ khi bạn truyền vào hàm, thủ tục một đối tượng, thì nó sẽ gọi copy constructor của kiểu dữ liệu đó để thực hiện 1 số sao chép. Nếu kiểu dữ liệu đó không có copy constructor thì nó sẽ tự gán giá trị của biến đó cho một biến khác. Ví dụ

Trong ví dụ trên khi bạn gọi max(m, n) ở đây bạn truyền vào tham trị nó sẽ gọi copy constructor của class số int và bạn hiểu là phương thức đó thực hiện sao chép dữ liệu để nếu bạn có thay đổi giá trị a b ở trong hàm thì 2 biến m, n ở ngoài truyền vào sẽ không ảnh hưởng.

Sau khi xử lí xong, và thoát khỏi hàm, nó sẽ gọi hàm destructor để hủy các biến mà nó tạo ra, trong trường hợp này bao gồm những biến được tạo bằng constructor và Copy constructor. Phần về Destructor mình sẽ nói bên dưới.

Như vậy nếu đối tượng của mình có dữ liệu là một pointer cấp phát động, chẳng hạn mình sẽ tạo ra một class chứa mảng số nguyên như sau:

Ở ví dụ trên khi khởi tạo đối tượng mình sẽ cho nó cấp phát động mảng A, và theo nguyên tắc thông thường, có cấp phát động phải có thu hồi bộ nhớ, nên mình sẽ thu hồi bộ nhớ ở destructor, và bây giờ mình khai báo MangInt Array1;  sau đó truyền Array1 vào hàm vidu

Như mô tả bên trên nó sẽ gọi copy constructor nhưng mình chưa khai báo cho nó nên mặc định nó sẽ làm là

– Gán Array1.n cho array.n

– Gán Array1.A cho array.A (lưu ý đây là pointer nên nó chép địa chỉ)

Sau khi xử lí xong hàm, lúc thoát khỏi hàm nó sẽ gọi Destructor cho những gì nó tạo khi chạy hàm đó. Như vậy bao gồm nó sẽ gọi destructor của array (trong đây nó thu hồi bộ nhớ của array)

Như vậy sau khi chạy xong hàm vidu trong main() thì nó sẽ kết thúc hàm main và vẫn theo nguyên tắc cũ là gọi Destructor cho những gì nó đã tạo trong hàm đó. Lúc này Array1 được gọi hủy. Theo như trong code hàm hủy, mình delete pointer A, mà địa chỉ pointer A đó đã bị hủy trước đó trong hàm vidu, nên lúc này xảy ra hiện tượng lỗi thu hồi 2 lần trên cùng một vùng nhớ.

Chính vì thế mà vai trò Copy Constructor là khi nó được chạy thì nó sẽ tạo ra vùng nhớ mới bằng cách cấp phát động và gán giá trị qua chứ không phải gán địa chỉ của 2 pointer theo cách mặc định của c++

2. Cài đặt Copy Constructor như thế nào?

“Bài viết này mình nói khá dài dòng, nhưng bạn chỉ cần nhớ một câu duy nhất Khi nào dữ liệu của class có cấp phát động, hãy cài đặt Copy Constructor

a. Khai báo copy constructor

b. Ví dụ về khai báo copy constructor

Mình sẽ lấy ví dụ về MangInt bên trên để viết copy constructor cho nó

3. Bài tâp ứng dụng Copy Constructor

a. Đề bài

Viết định nghĩa lớp String để biểu diễn khái niệm chuỗi ký tự với các phương thức thiết lập và huỷ bỏ, các hàm thành phần tính chiều dài chuỗi, so sánh hai chuỗi, nối hai chuỗi, đảo chuỗi, xuất chuỗi.

Lưu ý: không được sử dụng kiểu string của C++ (của thư viện chuẩn <string>)

Ở bài này theo yêu cầu bạn nên tạo một class có 2 dữ liệu là poiner thể hiện mảng kí tự char và một biến số nguyên lưu độ dài xâu.

b. Code lời giải

Bài này mình đã làm quá lâu rồi, nên một số chỗ chưa hợp lý, tuy nhiên các bạn có thể tham khảo để hiểu về cách ứng dụng Copy Constructor và viết lại theo cách riêng của mình.

String.h

String.cpp

Main.cpp

0