Bài viết được cho phép bởi tác giả Nguyễn Thái Dương
Những ai từng học lập trình hướng đối tượng OOP chắc chắn đều biết đến khái niệm nạp chồng (Override). Nhưng thông thường, ít bạn quan tâm đến việc compiler đã xử lý nó như thế nào. Phía sau hậu trường hệ thống đã làm gì để tạo nên điều kì diệu? Bài viết này hi vọng sẽ giúp các bạn có câu trả lời xác đáng nhất.
class Sample1 {
public:
virtual void vMethod1() {
printf(“This is virtual method 1n”);
}
virtual void vMethod2() {
printf(“This is virtual method 2n”);
}
void method2() {
printf(“This is NONVIRTUAL method 2n”);
}
public:
int member1;
int member2;
};
//Define method type
typedef void (*MyFunc)();
int main(int argc, const char * argv[]) {
Sample1 *a = new Sample1();
printf(“size of Sample1: %dn”, sizeof(Sample1));
MyFunc *virtualMethodTable = (MyFunc*)(*(MyFunc*)a);
virtualMethodTable[0](); //call method1
virtualMethodTable[1](); //call method2
return 0;
}
Kết quả sẽ như sau:
This is virtual method 2
Yeah!!. Đến đây bạn thấy không? Chúng ta có thể call được class method mà không cần gọi thông qua instance của nó. Ta hãy thử đi phân tích cấu trúc dữ liệu 16 byte của class Sample1:
#
vị trí (byte)
size (bytes)
field
1
0
8
con trỏ trỏ tới virtual method table (VMT)
2
8
4
member1
3
12
4
member2
Chỗ này có 1 khái niệm mới là Virtual Method Table (VMT). Vậy nó là cái gì? Virtual Method Table thực chất là một mảng các con trỏ hàm chứa địa chỉ của các hàm virtual trong class. Trong ví dụ trên sẽ là:
#
Vị trí (byte)
Size (bytes)
Field
1
0
8
địa chỉ của vMethod1
2
8
8
địa chỉ của vMethod2
Lỗi Could not create the Java Virtual Machine khi chạy Minecraft
Hướng dẫn cài đặt VirtualBox trên Ubuntu chi tiết nhất
OK, có 1 điều hơi lấn cấn ở đây khi bạn sử dụng member1 trong vMethod1:
class Sample1 {
public:
virtual void vMethod1() {
printf(“This is virtual method 1n”);
printf(“member1 = %dn”, member1);
}
virtual void vMethod2() {
printf(“This is virtual method 2n”);
}
void method2() {
printf(“This is NONVIRTUAL method 2n”);
}
public:
int member1;
int member2;
};
//Define method type
typedef void (*MyFunc)();
int main(int argc, const char * argv[]) {
Sample1 *a = new Sample1();
printf(“size of Sample1: %dn”, sizeof(Sample1));
MyFunc *virtualMethodTable = (MyFunc*)(*(MyFunc*)a);
virtualMethodTable[0](); //call method1
virtualMethodTable[1](); //call method2
return 0;
}
Kết quả sẽ có dạng như sau:
This is virtual method 2
giá trị member1 là 1 giá trị không phải là 1000 như mình đã gán ở trên mà là 1 giá trị rác. Nguyên nhân là do lời gọi virtualMethodTable[0](); chỉ đơn thuần là gọi 1 đoạn code của hàm mà chưa truyền con trỏ this vào trong hàm đó (vMethod1).
Giờ chúng ta sẽ tìm cách truyền con trỏ a vào cho lời gọi virtualMethodTable[0]();. Giờ ta sửa lại 1 chút:
class Sample1 {
public:
virtual void vMethod1() {
printf(“This is virtual method 1n”);
printf(“member1 = %dn”, member1);
}
virtual void vMethod2() {
printf(“This is virtual method 2n”);
}
void method2() {
printf(“This is NONVIRTUAL method 2n”);
}
public:
int member1;
int member2;
};
//Define method type
//Thêm tham số thisPtr ở đây
typedef void (*MyFunc)(Sample1 *thisPtr);
int main(int argc, const char * argv[]) {
Sample1 *a = new Sample1();
printf(“size of Sample1: %dn”, sizeof(Sample1));
MyFunc *virtualMethodTable = (MyFunc*)(*(MyFunc*)a);
//Lệnh trên tương đương với:
// memcpy(&virtualMethodTable2, b, 8);
virtualMethodTable[0](a); //call method1
virtualMethodTable[1](a); //call method2
return 0;
}
This is virtual method 2
class Sample1 {
public:
virtual void vMethod1() {
printf(“This is virtual method 1n”);
printf(“member1 = %dn”, member1);
}
virtual void vMethod2() {
printf(“This is virtual method 2n”);
}
void method2() {
printf(“This is NONVIRTUAL method 2n”);
}
public:
int member1;
int member2;
};
//Define method type
//Thêm tham số thisPtr ở đây
typedef void (*MyFunc)(Sample1 *thisPtr);
struct ClassDesc {
MyFunc* vmt;
int member1;
int member2;
};
void fakeVMethod1(ClassDesc* thisPtr) {
}
void fakeVMethod2(ClassDesc* thisPtr) {
}
void constructor(ClassDesc* desc) {
//Init virtual method table
}
void destructor(ClassDesc* desc) {
//delete virtual method table
}
int main(int argc, const char * argv[]) {
Sample1 *a = new Sample1();
printf(“size of Sample1: %dn”, sizeof(Sample1));
MyFunc *virtualMethodTable = (MyFunc*)(*(MyFunc*)a);
virtualMethodTable[0](a); //call method1
virtualMethodTable[1](a); //call method2
destructor(clsDesc); //
return 0;
}
Kết quả:
fake virtual method2 – member2: 3000
Tham khảo việc làm Java hấp dẫn trên TopDev
Đến đây chúng ta ít nhiều đã hình dung ra được cách C++ tổ chức dữ liệu trong một class như thế nào. Giờ chúng ta sẽ cùng tìm hiểu xem cách mà C++ override một method trong Class như thế nào:
class Sample1 {
public:
virtual void vMethod1() {
printf(“This is virtual method 1n”);
printf(“Sample1: member1 = %dn”, member1);
}
virtual void vMethod2() {
printf(“Sample1: This is virtual method 2n”);
}
void method2() {
printf(“This is NONVIRTUAL method 2n”);
}
public:
int member1;
int member2;
};
class Sample2: public Sample1 {
public:
//override vMethod1()
virtual void vMethod1() {
printf(“Sample2: This is virtual method 1n”);
}
virtual void vMethod3() {
printf(“Sample2: This is virtual method 3n”);
}
};
//Define method type
//Thêm tham số thisPtr ở đây
typedef void (*MyFunc)(Sample1 *thisPtr);
int main(int argc, const char * argv[]) {
Sample1 *a = new Sample1();
MyFunc *virtualMethodTable = (MyFunc*)(*(MyFunc*)a);
Sample2 *b = new Sample2();
MyFunc* virtualMethodTable2;
memcpy(&virtualMethodTable2, b, 8);
printf(“nn”);
printf(“Sample1 – vMethod1 Addr: %pn”, virtualMethodTable[0]);
printf(“Sample1 – vMethod2 Addr: %pn”, virtualMethodTable[1]);
printf(“Sample2 – vMethod1 Addr: %pn”, virtualMethodTable2[0]);
printf(“Sample2 – vMethod2 Addr: %pn”, virtualMethodTable2[1]);
return 0;
}
Kết quả:
Sample2 – vMethod2 Addr: 0x400950
Chúng ta thấy ngay, Class B được override method1 nên địa chỉ của vMethod1 trong VMT của b khác với của a, trong khi vMethod2 thì giống hết nhau vì không bị override.
Ta có thể fake lại việc override một cách đơn giản như sau:
class Sample1 {
public:
virtual void vMethod1() {
printf(“This is virtual method 1n”);
printf(“member1 = %dn”, member1);
}
virtual void vMethod2() {
printf(“This is virtual method 2n”);
}
void method2() {
printf(“This is NONVIRTUAL method 2n”);
}
public:
int member1;
int member2;
};
//Define method type
//Thêm tham số thisPtr ở đây
typedef void (*MyFunc)(Sample1 *thisPtr);
struct ClassDesc {
MyFunc* vmt;
int member1;
int member2;
};
void fakeVMethod1(ClassDesc* thisPtr) {
}
void fakeVMethod2(ClassDesc* thisPtr) {
}
void constructor(ClassDesc* desc) {
//Init virtual method table
}
void destructor(ClassDesc* desc) {
//delete virtual method table
}
void override_fakeVMethod1(ClassDesc* thisPtr) {
}
void constructor_Extend(ClassDesc* desc) {
constructor(desc);
}
int main(int argc, const char * argv[]) {
Sample1 *a = new Sample1();
printf(“size of Sample1: %dn”, sizeof(Sample1));
MyFunc *virtualMethodTable = (MyFunc*)(*(MyFunc*)a);
virtualMethodTable[0](a); //call method1
virtualMethodTable[1](a); //call method2
destructor(clsDesc); //
destructor(clsDesc2); //
return 0;
}
Kết quả:
This is virtual method 2
————–FAKE CLASS—————-
fake virtual method2 – member2: 3000
————–FAKE INHERITANCE CLASS—————-
fake virtual method2 – member2: 3000
Bài viết này hi vọng mang đến cho các bạn 1 cái nhìn rõ hơn về khía cạnh cài đặt của virtual method và override – cái mà trình biên dịch đã che dấu khỏi developer. Chúng ta sẽ cùng nhau tìm hiểu những điều thú vị khác nằm sâu bên trong chương trình để hiểu rõ hơn cách thức mà máy tính hoạt động đằng sau những dòng code của bạn.
Xin cảm ơn và hẹn gặp lại!
Bài viết gốc được đăng tải tại codetoanbug