2011-11-16 40 views
5

Tôi đọc về thừa kế và tôi có một vấn đề lớn mà tôi đã không thể giải quyết trong nhiều giờ:Inheritance ảo Lẫn lộn

Cho một lớp Bar là một lớp học với virtual chức năng,

class Bar 
{ 
    virtual void Cook(); 
}; 

là gì khác nhau giữa:

class Foo : public Bar 
{ 
    virtual void Cook(); 
}; 

class Foo : public virtual Bar 
{ 
    virtual void Cook(); 
}; 

? Giờ của Googling và đọc đã đưa ra rất nhiều thông tin về việc sử dụng nó, nhưng không ai thực sự cho tôi biết sự khác biệt giữa hai người là gì và chỉ làm tôi bối rối hơn.

+2

Tôi sẽ không trả lời vì chủ đề không thực sự đáng được xử lý nông như vậy: nhưng không có 'virtual' mỗi lớp thừa kế từ' Bar' sẽ có bản sao của 'Bar', với' virtual' lớp dẫn xuất nhất sẽ chỉ có một bản sao của 'Bar'. –

+0

Thử: [tìm kiếm này] (http://stackoverflow.com/search?q = virtual + inheritance +% 5Bc% 2B% 2B% 5D) –

+0

có thể trùng lặp của [In C++ virtual base class?] (http://stackoverflow.com/questions/21558/in-c-virtual-base-class) –

Trả lời

4

Thừa kế ảo chỉ có liên quan nếu các lớp học được kế thừa từ Foo.Nếu tôi xác định như sau:

class B {}; 
class L : virtual public B {}; 
class R : virtual public B {}; 
class D : public L, public R {}; 

Sau đó, đối tượng cuối cùng sẽ chỉ chứa một bản sao của B, chia sẻ bởi cả hai LR. Nếu không có virtual, một đối tượng thuộc loại D sẽ chứa hai bản sao của B, một trong số L và một trong số R.

Có một số tranh luận rằng tất cả thừa kế phải là ảo (vì trong trường hợp tạo sự khác biệt, đó là điều bạn muốn nhất trong số thời gian). Tuy nhiên, trong thực tế, thừa kế ảo rất đắt và trong hầu hết các trường hợp, không cần thiết: trong một hệ thống được thiết kế tốt, hầu hết thừa kế sẽ đơn giản là lớp bê tông kế thừa từ một hoặc nhiều giao diện hơn; một lớp bê tông như vậy thường không được thiết kế để được bắt nguồn từ chính nó, vì vậy không có vấn đề gì. Nhưng có một số trường hợp ngoại lệ quan trọng: nếu, ví dụ, bạn định nghĩa một giao diện, và sau đó là mở rộng cho giao diện, phần mở rộng sẽ thừa kế hầu như từ giao diện cơ sở, vì việc triển khai cụ thể có thể muốn triển khai một số tiện ích mở rộng. Hoặc nếu bạn đang thiết kế mixin, trong đó một số lớp nhất định chỉ thực hiện một phần của giao diện và lớp cuối cùng được thừa kế từ một vài trong số các lớp này (một phần của giao diện ). Cuối cùng, các criteron là liệu kế thừa hầu hay không không phải là quá khó khăn:

  • nếu thừa kế không được công khai, thì có lẽ không nên ảo (Tôi chưa từng thấy một ngoại lệ), nếu không

  • nếu lớp không được thiết kế để trở thành một lớp cơ sở, không có nhu cầu thừa kế ảo, nếu không

  • thừa kế nên ảo.

Có một vài ngoại lệ, nhưng các quy tắc trên sai ở bên cạnh an toàn ; thường là "chính xác" để kế thừa hầu như ngay cả trong trường hợp thừa kế ảo là không cần thiết.

Một điểm cuối cùng: một cơ sở ảo phải luôn luôn được khởi tạo bởi lớp nhất có nguồn gốc, không lớp mà trực tiếp thừa hưởng (và tuyên bố rằng các thừa kế là ảo). Tuy nhiên, trong thực tế, đây không phải là vấn đề. Nếu bạn xem xét các trường hợp thừa kế ảo có ý nghĩa, thì luôn là trường hợp kế thừa từ giao diện, sẽ không chứa dữ liệu và do đó chỉ có một hàm tạo mặc định. Nếu bạn thấy mình kế thừa hầu như từ các lớp học với các nhà thầu thực hiện các đối số , đã đến lúc đặt một số câu hỏi nghiêm túc về thiết kế.

5

Chức năng khôn ngoan không có nhiều khác biệt giữa 2 phiên bản. Với trường hợp thừa kế virtual, mọi triển khai thường thêm một con trỏ (vptr thích) (giống như trong trường hợp của các hàm virtual). Giúp để tránh nhiều bản sao lớp cơ sở được tạo ra do nhiều thừa kế (vấn đề diamond inheritance)

Ngoài ra, virtual thừa kế đại biểu quyền gọi constructor của lớp cơ sở của nó. Ví dụ,

class Bar; 
class Foo : public virtual Bar 
class Other : public Foo // <--- one more level child class 

Vì vậy, bây giờ Bar::Bar() sẽ được gọi trực tiếp từ Other::Other() và cũng sẽ được đặt ở vị trí đầu tiên trong các lớp cơ sở khác.

này đoàn tính năng giúp trong việc thực hiện một chức năng final class (trong Java) trong C++ 03:

class Final { 
    Final() {} 
    friend class LastClass; 
}; 

class LastClass : virtual Final { // <--- 'LastClass' is not derivable 
... 
}; 

class Child : public LastClass { // <--- not possible to have object of 'Child' 
}; 
3

Trong trường hợp này, không có sự khác biệt. thừa kế ảo có liên quan đến các trường hợp chia sẻ cha subobjects bởi các lớp thừa kế

struct A 
{ 
    int a; 
}; 

struct B : public virtual A 
{ 
    int b; 
} 

struct C : public virtual A 
{ 
    int c; 
}; 

struct D : public B, public C 
{ 
}; 

Có một bản duy nhất của biến thành viên a trong trường hợp của D; Nếu A không phải là lớp cơ sở ảo, sẽ có hai trường hợp phụ là A, ví dụ: D.

+0

"_Trong trường hợp này, không có sự khác biệt._" cho đến khi bạn cố gắng sử dụng 'static_cast'! – curiousguy

0

Chức năng ảo là một hàm có thể sẽ có triển khai khác nhau trong lớp dẫn xuất (mặc dù nó không phải là phải).

Trong ví dụ cuối cùng của bạn là thừa kế ảo. Hãy tưởng tượng một trường hợp mà bạn có hai lớp (A và B) bắt nguồn từ một lớp cơ sở (chúng ta hãy gọi nó là 'Base'). Bây giờ hãy tưởng tượng một lớp thứ ba C bắt nguồn từ A và B. Nếu không có thừa kế ảo, C sẽ chứa hai bản sao của 'Base'. Điều đó có thể dẫn đến sự mơ hồ trong khi biên soạn. Điều quan trọng trong kế thừa ảo là các tham số cho hàm tạo lớp 'Base' (nếu có) PHẢI được cung cấp trong lớp C, bởi vì các cuộc gọi như vậy từ A và B sẽ bị bỏ qua.

+0

"_Đó có thể dẫn đến sự mơ hồ khi biên dịch._" ** này sẽ ** dẫn đến sự mơ hồ _iff_ bạn thử 'C' IS-A' Base'. – curiousguy