Nội dung bài viết
Đăng ký học lập trình C++
Tại STDIO bạn được dạy nền tảng lập trình tốt nhất.
Đăng ký học
Nguyễn Nghĩa Phương pháp lập trình hướng đối tượng Object-Oriented Programming có tính chất: Tính trừu tượng hóa, tính đóng gói, tính kế thừa, tính đa hình. Trong đó tính đa hình được thể hiện qua con trỏ và hàm ảo (virtual function). Nếu các bạn biết tính đa hình thì chắc chắn hàm ảo khá là quen thuộc. Nhưng khái niệm phương thức ảo có lẽ là một khái niệm khá mới mẻ. Nó được định nghĩa như thế nào, chức năng, sử dụng nó ra sao thì bài viết này tác giả sẽ trình bày chi tiết một cách cụ thể.

Giới thiệu

Phương pháp lập trình hướng đối tượng Object-Oriented Programming có 4 tính chất: Tính trừu tượng hóa, tính đóng gói, tính kế thừa, tính đa hình. Trong đó tính đa hình được thể hiện qua con trỏ và hàm ảo (virtual function). Nếu các bạn biết tính đa hình thì chắc chắn hàm ảo khá là quen thuộc. Nhưng khái niệm phương thức hủy ảo có lẽ là một khái niệm khá mới mẻ. Nó được định nghĩa như thế nào, chức năng, sử dụng nó ra sao thì bài viết này tác giả sẽ trình bày chi tiết một cách cụ thể.

Tiền đề bài viết

Bài viết này xuất phát từ sở thích chia sẽ kiến thức của tác giả. Bài viết này gửi tới những lập trình viên đang đặt ra những câu hỏi như Virtual Destructor là gì? Vì sao cần phải dùng nó?...

Đối tượng hướng đến

Những lập trình viên đã có nhiều kinh nghiệm với lập trình hướng đối tượng với ngôn ngữ C++, hiểu rõ bản chất của phương thức hủy.

Các bạn có thể xem lại các bài viết về lập trình hướng đối tượng của tác giả La Kiến Vinh:

Destructor

Destructor là một phương thức của một lớp và tự động được gọi trước khi đối tượng bị thu hồi (thoát khỏi phạm vi, chương trình kết thúc, sử dụng delete). Vì đa số chúng ta sử dụng phương thức này để thực hiện dọn dẹp tài nguyên của đối tượng (tránh trường hợp lost memory) nên nó được gọi là phương thức hủy.

Phương thức hủy được khai báo có tên trùng với tên lớp và thêm kí tự ~ ở trước tên phương thức và không có giá trị trả về.

~Product();

Virtual Destructor

Trong một lớp thì Destructor có thể đánh dấu làm hàm ảo còn Constructor thì không được đánh dấu là hàm ảo.

virtual Product();   //illegal
virtual ~Product();  //legal

Xét một vài ví dụ để làm rõ Virtual Destructor. Ví dụ chúng ta có một lớp cha Base và một lớp con Derived được hiện thực như dưới đây.

Trường hợp 1: Phương thức lớp cha không đánh dấu Virtual

Phương thức hủy lớp cha không được đánh dấu là hàm ảo:

class Base
{
public:
	Base(){};
	~Base() { cout << "Destructor Base\n"; };
};


class Derived : public Base
{
public:
	Derived(){};
	~Derived() { cout << "Destructor Derived\n"; }
};

Hàm main thực hiện upcasting từ lớp con lên lớp cha.

int main()
{
	Base *b = new Derived();
	delete b;
	return 0;
}

Sau khi Build và Debug chúng ta thấy chỉ có dòng "Destructor Base" được xuất ra, có nghĩa là chỉ hàm phương thức lớp cha được gọi nhưng phương thức của lớp con không được gọi. Dẫn đến mất mát bộ nhớ.

Xét tiếp ví dụ dưới đây. Tương tự như ví dụ trên nhưng có sửa đổi ở lớp Derived

class Base
{

public:
	Base(){};
	~Base() { cout << "Destructor Base\n"; };
};


class Derived : public Base
{
private:
	int* m_array;
public:
	Derived(){ this->m_array = new int[1024]; };
	~Derived()
	{
		cout << "Destructor Derived\n";
		delete this->m_array;
	}
};

Hàm main vẫn giữ nguyên như ví dụ ta xét ở đầu.

int main()
{
	Base *b = new Derived();
	delete b;
	return 0;
}

Với lớp con như trên thì mặc dù chúng ta có định nghĩa phương thức để giải phóng m_array nhưng phương thức của lớp con không được gọi. Có nghĩa là chúng ta đã mất đi 1024*4 bytes bộ nhớ.

Trường hợp 2: Phương thức lớp cha có đánh dấu Virtual

Phương thức lớp cha được đánh dấu là phương thức ảo:

class Base
{
public:
	Base(){};
	virtual ~Base() { cout << "Destructor Base\n"; };
};


class Derived : public Base
{
public:
	Derived(){};
	~Derived() { cout << "Destructor Derived\n"; }
};

Thực hiện hàm main giống như trường hợp 1

int main()
{
	Base *b = new Derived();
	delete b;
	return 0;
}

Bulid và Debug thì chương trình xuất ra hai dòng là

Destructor Derived
Destructor Base

Như vậy là phương thức hủy của lớp con được gọi trước sau đó mới gọi phương thức hủy lớp cha, đúng như chúng ta mong muốn. Tránh được tình trạng mất bộ nhớ.

Lời kết

Qua bài viết này hy vọng tác giả đang mang đến cho các bạn một kiến thức khá bổ ích về Virtual Destructor. Nếu có bất cứ thắc mắc nào các bạn có thể để lại bình luận phía dưới hoặc liên hệ với tác giả với Nguyễn Nghĩa để được giải đáp sớm nhất.

THẢO LUẬN
ĐÓNG