Hàm ảo (Virtual Function), override và final là những khái niệm quan trọng trong lập trình hướng đối tượng với C++, đặc biệt khi làm việc với tính đa hình (polymorphism). Bài viết này sẽ đi sâu vào từng khái niệm, kèm theo ví dụ minh họa dễ hiểu, giúp bạn nắm vững kiến thức và áp dụng hiệu quả trong dự án của mình.
Hàm ảo (Virtual Function) trong C++
Hàm ảo là một hàm thành viên được khai báo trong lớp cơ sở (base class) và có thể được định nghĩa lại (override) trong lớp dẫn xuất (derived class). Từ khóa virtual được sử dụng để khai báo hàm ảo. Điểm đặc biệt của hàm ảo là nó cho phép gọi đúng phiên bản của hàm dựa trên kiểu thực tế của đối tượng, ngay cả khi đối tượng đó được truy cập thông qua một con trỏ hoặc tham chiếu kiểu lớp cơ sở. Điều này tạo nên tính đa hình trong C++.
Alt: Ví dụ code minh họa hàm ảo (virtual function) trong C++.
Ví dụ:
#include <iostream>
using namespace std;
class Sun {
public:
virtual void showcty() {
cout << "Hien thi sun:" << endl;
}
void display() {
cout << "Trung bay sun:" << endl;
}
};
class kethua : public Sun {
public:
void display() {
cout << "Trung bay sun1:" << endl;
}
void showcty() {
cout << "Hien thi sun1:" << endl;
}
};
int main() {
Sun obj1;
Sun *p;
cout << "P tro toi sun:" << endl;
p = &obj1;
p->display();
p->showcty();
cout << "P tro toi sun1:" << endl;
kethua obj2;
p = &obj2;
p->display();
p->showcty();
return 0;
}
Trong ví dụ này, showcty() là một hàm ảo. Khi con trỏ p của lớp Sun trỏ đến đối tượng obj1 (kiểu Sun), p->showcty() sẽ gọi Sun::showcty(). Nhưng khi p trỏ đến obj2 (kiểu kethua), p->showcty() sẽ gọi kethua::showcty(). Hàm display() không phải là hàm ảo, do đó nó luôn gọi phiên bản của lớp Sun.
Hàm ảo thuần túy (Pure Virtual Function):
Hàm ảo thuần túy là một hàm ảo không có định nghĩa trong lớp cơ sở. Nó được khai báo bằng cách sử dụng = 0. Một lớp chứa ít nhất một hàm ảo thuần túy được gọi là lớp trừu tượng (abstract class). Lớp trừu tượng không thể được khởi tạo trực tiếp, mà chỉ có thể được sử dụng làm lớp cơ sở cho các lớp dẫn xuất. Lớp dẫn xuất phải định nghĩa lại tất cả các hàm ảo thuần túy của lớp cơ sở, nếu không nó cũng sẽ là một lớp trừu tượng.
Ví dụ:
virtual void setsun(int i) = 0;
Alt: Code ví dụ về hàm hủy ảo (virtual destructor) trong C++.
Hàm hủy ảo (Virtual Destructor):
Một trường hợp quan trọng khác là hàm hủy ảo. Nếu bạn có một lớp cơ sở có hàm hủy không ảo và bạn hủy một đối tượng lớp dẫn xuất thông qua một con trỏ lớp cơ sở, thì chỉ hàm hủy của lớp cơ sở được gọi. Để đảm bảo rằng hàm hủy của cả lớp cơ sở và lớp dẫn xuất đều được gọi, bạn nên khai báo hàm hủy của lớp cơ sở là ảo.
Ví dụ:
#include <iostream>
using namespace std;
class Sun {
public:
virtual ~Sun() {
cout << "This is Sun*" << endl;
}
};
class Sun1 : public Sun {
public:
~Sun1() {
cout << "This is Sun1" << endl;
}
};
int main() {
Sun* sun = new Sun1();
delete sun;
return 0;
}
Nếu hàm hủy của Sun không phải là ảo, chỉ có “This is Sun” được in ra. Khi nó là ảo, cả “This is Sun1” và “This is Sun” đều được in ra, đảm bảo giải phóng tài nguyên đúng cách.
Override trong C++
Override là việc định nghĩa lại một phương thức (hàm thành viên) đã có trong lớp cơ sở, ở lớp dẫn xuất. Khi một phương thức trong lớp dẫn xuất có cùng tên, tham số và kiểu trả về với một phương thức ảo trong lớp cơ sở, phương thức đó được gọi là override phương thức của lớp cơ sở.
Alt: Ví dụ code về override một virtual function của base class trong C++.
Ví dụ:
#include <iostream>
using namespace std;
class Sun {
public:
virtual void Thongtin() {
cout << "Day la ham thong tin cty SUN" << endl;
}
};
class CSun1 : public Sun {
public:
void Thongtin() const {
cout << "Day la ham thong tin nhan vien Sun" << endl;
}
};
Trong ví dụ trên, CSun1::Thongtin không override Sun::Thongtin vì CSun1::Thongtin là const, còn Sun::Thongtin thì không. Để tránh những lỗi tương tự, C++11 giới thiệu từ khóa override.
Từ khóa override:
Từ khóa override được sử dụng để chỉ rõ rằng một hàm thành viên trong lớp dẫn xuất dự định override một hàm thành viên ảo từ lớp cơ sở. Nếu hàm không thực sự override một hàm ảo, trình biên dịch sẽ báo lỗi.
Ví dụ:
class Sun {
public:
virtual char* getThongtinsun(int sign) {
if (sign > 0) {
return "Sun";
}
return "";
}
};
class CSun1: public Sun {
public:
char* getThongtinsun(char sign) override { // Lỗi biên dịch: không override
if (sign > 0) {
return "CSun1";
}
return "";
}
};
Trong ví dụ này, trình biên dịch sẽ báo lỗi vì CSun1::getThongtinsun có kiểu tham số khác với Sun::getThongtinsun, do đó nó không override hàm ảo.
Final trong C++
Từ khóa final được sử dụng để ngăn chặn việc override một hàm ảo trong các lớp dẫn xuất tiếp theo. Khi một hàm ảo được đánh dấu là final, bất kỳ lớp dẫn xuất nào cố gắng override hàm đó sẽ gây ra lỗi biên dịch.
Alt: Ví dụ code về virtual function được đánh dấu là final trong C++.
Ví dụ:
class Sun {
public:
virtual void Thongtin() final {
cout << "Day la ham thong tin cty Sun" << endl;
}
};
class CSun1 : public Sun {
public:
// Error
void Thongtin() override {
cout << "Day la ham thong tin nhan vien Sun" << endl;
}
};
Trong ví dụ này, CSun1::Thongtin sẽ gây ra lỗi biên dịch vì nó cố gắng override hàm Thongtin đã được đánh dấu là final trong lớp Sun.
Tóm lại, virtual, override và final là những công cụ mạnh mẽ trong C++ để xây dựng các hệ thống phân cấp lớp linh hoạt và an toàn. Hiểu rõ cách sử dụng chúng sẽ giúp bạn viết mã rõ ràng, dễ bảo trì và ít lỗi hơn.
