Tuesday, May 3, 2011

Khái niệm Static binding, Dynamic Binding, Virtual function, Polymorphism, Override và Hide trong C++

Khái niệm Static binding, Dynamic Binding, Virtual function, Polymorphism, Override và Hide trong C++.
The concept of Static binding, Dynamic Binding, Virtual function, Polymorphism, Override and Hide a function in C++.


Lý do để ta đề cập đến nhiều vấn đề một lúc là bởi vì chúng có liên quan đến nhau một cách mật thiết. Bên cạnh đó đây đều là các khái niệm mang tính cơ sở của C++. Các khái niệm này cần thiết để đọc sách về C++ vì ta có thể bắt gặp trong bất kỳ cuốn sách nào về C++.

Về cơ bản mặc định trong C++ sử dụng Static binding, trừ khi muốn sử dụng riêng dynamic binding cho 1 hàm nào đó thì ta phải khai báo thêm từ khóa Virtual ở lời khai báo hàm, như vậy compiler sẽ hiểu cần thực hiên dynamic binding cho hàm đã nêu.
Để rõ hơn ta xem xét ví dụ về static binding sau:
  1. #include  
  2. using namespace std; 
  3.  
  4. struct A { 
  5. void f() { cout << "Class A" << endl; } 
  6. }; 
  7.  
  8. struct B: A {
  9. void f() { cout << "Class B" << endl; } 
  10. }; 
  11.  
  12. void g(A& arg) { 
  13. arg.f(); 
  14.  
  15. int main() { 
  16. B x; 
  17. g(x); 
  18. }
  19.  
Đây là một ví dụ về Static binding trong đó ta nhận thấy, khi hàm g(x) được gọi thìhàm A::f() sẽ được gọi dù ta đã định nghĩa kiểu của x là B. Do tại thời điểm biên dịch (compile-time) thì trình biên dịch chỉ có thể xác định được tham số của hàm g() sẽ là 1 tham chiếu tới biển có kiểu A hoặc là kiểu thừa kế của A. Như vậy trình biên dịch sẽ không thể xác định kiểu dữ liệu chính xác là kiểu A hay là kiểu B (ở đây B cũng là kế thừa của A). Như vậy kết quả của chương trình trên sẽ là

Class A

Tuy vậy tại thời điểm đang chay (runtime) thì chương trình có thể xác định được là kiểu dữ liệu nào. Muốn để thông báo cho chương trình làm việc này, hãy dùng từ khóa Virtual như ví dụ sau:
  1. #include  
  2. using namespace std; 
  3.  
  4. struct A { 
  5. virtual void f() { cout << "Class A" << endl; } 
  6. }; 
  7.  
  8. struct B: A { 
  9. void f() { cout << "Class B" << endl; } 
  10. }; 
  11.  
  12. void g(A& arg) { arg.f(); } 
  13.  
  14. int main() { 
  15. B x; 
  16. g(x); 
  17. }
 Ví dụ trên hàm A::f() đã được thêm từ khóa Virtual và kết quả phải là

Class B

Nên nhớ: Nếu A::f() được khai là Virtual thì mặc nhiên B::f() cũng là Virtual khi B là kế thừa của A và f() là identical (identical theo nghĩa là có cùng tên và cùng tham số). Như vậy dù không tường minh nhưng B::f() cũng là một hàm virtual và B::f() đã override A::f(). Tuy vậy hãy xem xét ví dụ sau để thấy được sự khác nhau giữa Override và Hide:
  1. #include  
  2. using namespace std; 
  3.  
  4. struct A { 
  5. virtual void f() { cout << "Class A" << endl; } 
  6. }; 
  7.  
  8. struct B: A { 
  9. void f(int) { cout << "Class B" << endl; } 
  10. }; 
  11.  
  12. struct C: B { 
  13. void f() { cout << "Class C" << endl; } 
  14. }; 
  15.  
  16. int main() { 
  17. B b; 
  18. C c; 
  19. A* pa1 = &b; 
  20. A* pa2 = &c; 
  21. // b.f(); 
  22. pa1->f(); 
  23. pa2->f(); 
  24. }
  25.  
Trong ví dụ trên kết quả sẽ phải là
Class A
Class C


Lý do vì B::f() không còn là hàm virtual do nó có tham số khác với hàm A::f(). Như vậy khi B::f() không còn là hàm Virtual thì trình biên dịch không thực hiện dynamic binding.Ở đây B::f() đã Hide A::f().
Tuy vậy C::f() lại là hàm Virtual vì nó giống nguyên so với A::f() dù C::f() không thể nhìn thấy A::f() hay A::f() là không visible đối với C. Ở đây C::f() đã Override A::f().
Có một hàm rất dễ nhận thấy là hàm Destructor. Hàm này thường được khai báo là Virtual chính do tính chất trên.

Một lớp có chứa hàm Virtual hay kế thừa hàm Virtual được gọi là một lớp có tính chất đa hình.

Còn một vấn đề nữa là giá trị trả về của các hàm Override. Tuy nhiên vấn đề này sẽ được thảo luận ở một bài khác.

No comments:

Post a Comment