2012-01-02 20 views
16

Các mã sau đây:Nhiều thừa kế và tinh khiết chức năng ảo

struct interface_base 
{ 
    virtual void foo() = 0; 
}; 

struct interface : public interface_base 
{ 
    virtual void bar() = 0; 
}; 

struct implementation_base : public interface_base 
{ 
    void foo(); 
}; 

struct implementation : public implementation_base, public interface 
{ 
    void bar(); 
}; 

int main() 
{ 
    implementation x; 
} 

thất bại trong việc biên dịch với các lỗi sau đây:

test.cpp: In function 'int main()': 
test.cpp:23:20: error: cannot declare variable 'x' to be of abstract type 'implementation' 
test.cpp:16:8: note: because the following virtual functions are pure within 'implementation': 
test.cpp:3:18: note: virtual void interface_base::foo() 

tôi đã chơi đùa với nó và tìm ra rằng làm cho 'giao diện - > interface_base 'và' implementation_base -> interface_base 'thừa kế ảo, khắc phục sự cố, nhưng tôi không hiểu tại sao. Ai đó có thể giải thích chuyện gì đang diễn ra không?

p.s. Tôi bỏ qua các destructors ảo nhằm mục đích làm cho đoạn mã ngắn hơn. Xin vui lòng không cho tôi biết để đưa chúng vào, tôi đã biết :)

+0

cũng xem: http://stackoverflow.com/questions/457609/diamond-inheritance-and-pure-virtual-functions – bdonlan

Trả lời

14

Bạn có haiinterface_base các lớp cơ sở trong cây thừa kế của bạn. Điều này có nghĩa là bạn phải cung cấp hai triển khai của foo(). Và kêu gọi một trong hai người họ sẽ thực sự khó xử, đòi hỏi nhiều diễn viên phải phân biệt. Điều này thường không phải là những gì bạn muốn.

Để giải quyết này, sử dụng thừa kế ảo:

struct interface_base 
{ 
    virtual void foo() = 0; 
}; 

struct interface : virtual public interface_base 
{ 
    virtual void bar() = 0; 
}; 

struct implementation_base : virtual public interface_base 
{ 
    void foo(); 
}; 

struct implementation : public implementation_base, virtual public interface 
{ 
    void bar(); 
}; 

int main() 
{ 
    implementation x; 
} 

Với thừa kế ảo, chỉ có một thể hiện của các lớp cơ sở trong câu hỏi được tạo ra trong hệ thống cấp bậc thừa kế cho đề cập đến tất cả các ảo. Do đó, chỉ có một foo(), có thể được đáp ứng bởi implementation_base::foo().

Để biết thêm thông tin, see this prior question - câu trả lời cung cấp một số sơ đồ đẹp để làm cho điều này rõ ràng hơn.

+0

+1: Đánh bại tôi. –

+0

Có cách nào cho tôi để nói "sử dụng việc thực hiện từ' implementation_base' "cho cả hai hiện thực của' foo() ', mà không sử dụng thừa kế ảo? – HighCommander4

+0

@ HighCommander4, bạn có thể lấy được giao diện từ 'implementation_base' hoặc ngược lại, do đó loại bỏ kim cương và tạo ra một cây kế thừa tuyến tính. Ngoài ra, không, nhưng nếu bạn muốn ẩn nó, bạn có thể làm cho các trình bao bọc rỗng 'interface_base' và' interface' hầu như xuất phát từ lớp giao diện _real_. – bdonlan

2

Đối với trường hợp 'giải quyết' vấn đề thừa kế kim cương, các giải pháp được cung cấp bởi bdonlan là hợp lệ. Có nói rằng, bạn có thể tránh được vấn đề kim cương với thiết kế. Tại sao mọi trường hợp của một lớp nhất định được xem là cả hai lớp? Bạn có bao giờ đi để vượt qua đối tượng này cùng một lớp học mà nói cái gì đó như:

void ConsumeFood(Food *food); 
void ConsumeDrink(Drink *drink); 

class NutritionalConsumable { 
    float calories() = 0; 
    float GetNutritionalValue(NUTRITION_ID nutrition) = 0; 
}; 
class Drink : public NutritionalConsumable { 
    void Sip() = 0; 
}; 
class Food : public NutritionalConsumable { 
    void Chew() = 0; 
}; 
class Icecream : public Drink, virtual public Food {}; 

void ConsumeNutrition(NutritionalConsumable *consumable) { 
    ConsumeFood(dynamic_cast<Food*>(food)); 
    ConsumeDrink(dynamic_cast<Drink*>(drink)); 
} 

// Or moreso 
void ConsumeIcecream(Icecream *icecream) { 
    ConsumeDrink(icecream); 
    ConsumeFood(icecream); 
} 

Chắc chắn nó sẽ tốt hơn trong trường hợp này cho Icecream chỉ thực hiện NutritionalConsumable và cung cấp một GetAsDrink()GetAsFood() phương pháp đó sẽ trở lại một proxy , hoàn toàn vì lợi ích của việc xuất hiện dưới dạng thức ăn hoặc đồ uống. Nếu không, điều đó gợi ý rằng có một phương pháp hoặc đối tượng chấp nhận Food nhưng bằng cách nào đó muốn xem sau đó là Drink, chỉ có thể đạt được với dynamic_cast và không cần thiết phải phù hợp với thiết kế phù hợp hơn.

+1

Ví dụ của bạn khá khác so với tôi.Trong trường hợp của tôi, 'implementation' xuất phát từ' interface' vì nó sẽ được sử dụng đa hình như một 'interface *', và nó xuất phát từ 'implementation_base' để tái sử dụng phần' interface_base' của 'interface'. – HighCommander4

+1

Ok, vậy ai cần xem nó dưới dạng 'implementation_base'? Và ai giao tiếp với 'giao diện' nhưng không giao tiếp' interface_base'? Suy nghĩ đằng sau 'implementation_base' kế thừa từ' interface_base' thay vì 'giao diện' là gì? Mặc dù câu hỏi của tôi khá trừu tượng, bạn có đang giải quyết một thiết kế hệ thống thực hay chỉ là vấn đề kim cương? –

+0

Không ai cần xem nó như là một 'implementation_base', tôi có thể chỉ xuất phát từ' giao diện' và nhân đôi việc thực hiện bên trong 'implementation_base' - nhưng tôi không muốn sao chép mọi thứ. – HighCommander4

8

Các thông thường C++ thành ngữ là:

  • công ảo thừa kế cho giao diện lớp
  • tin phi ảo thừa kế cho thực hiện lớp

tôi n trường hợp này chúng ta sẽ có:

struct interface_base 
{ 
    virtual void foo() = 0; 
}; 

struct interface : virtual public interface_base 
{ 
    virtual void bar() = 0; 
}; 

struct implementation_base : virtual public interface_base 
{ 
    void foo(); 
}; 

struct implementation : private implementation_base, 
         virtual public interface 
{ 
    void bar(); 
}; 

Trong implementation, độc đáo interface_base cơ sở ảo là:

  • công khai thừa hưởng qua interface: implementation --public ->interface --public ->interface_base
  • được kế thừa riêng thông qua implementation_base: implementation --private ->implementation_base - công khai ->interface_base

Khi mã khách hàng thực hiện một trong những nguồn gốc để chuyển đổi cơ sở:

  • nguồn gốc để chuyển đổi con trỏ cơ sở,
  • tham khảo ràng buộc của loại hình cơ bản với một initializer kiểu tĩnh có nguồn gốc,
  • tiếp cận với di truyền thành viên cấp cơ sở thông qua một lvalue của loại tĩnh có nguồn gốc,

những gì quan trọng chỉ là có ít nhất một quyền truy cập e thừa kế đường dẫn từ lớp dẫn xuất đến lớp con lớp cơ sở đã cho; các đường dẫn không thể tiếp cận khác được bỏ qua. Bởi vì thừa kế của lớp cơ sở chỉ là ảo ở đây, chỉ có một chủ đề lớp cơ sở nên các chuyển đổi này không bao giờ mơ hồ.

Ở đây, chuyển đổi từ implementation thành interface_base, luôn có thể được thực hiện bằng mã khách hàng qua interface; con đường không thể tiếp cận khác không quan trọng chút nào. Cơ sở ảo interface_base độc đáo được kế thừa công khai từ implementation.

Trong nhiều trường hợp, các lớp thực hiện (implementation, implementation_base) sẽ được giữ ẩn từ mã khách hàng: chỉ con trỏ hoặc tham chiếu đến các lớp giao diện (interface, interface_base) sẽ được tiếp xúc.

+0

Tôi sẽ không nói rằng" thừa kế công khai ảo cho các lớp giao diện "là" (hoặc a) thành ngữ C++ thông thường ". Có lẽ nó nên, nhưng từ những gì tôi có thể nói nó chắc chắn là không. –

+0

@PaulGroke Hum .. bạn có thể đúng. Thành ngữ thích hợp? đề nghị thành ngữ? – curiousguy

+0

Tôi đã viết "có lẽ nó nên", bởi vì tôi thành thật không chắc chắn nếu nó nên. Tôi gần như không bao giờ sử dụng thừa kế ảo (trong thực tế, tôi không chắc chắn nếu tôi * bao giờ * sử dụng nó trong mã sản xuất). Vì vậy, tôi không biết những vấn đề nó có thể gây ra và những vấn đề nào cũng ảnh hưởng đến thừa kế ảo với các lớp giao diện thuần túy (tức là ctor tầm thường, dtor tầm thường, không có dữ liệu). –