7

tôi đã thực hiện một mã kiểm tra như sau:mơ hồ trong đa kế thừa giao diện trong C++

#include <iostream> 
using namespace std; 

#ifndef interface 
#define interface struct 
#endif 

interface Base 
{ 
    virtual void funcBase() = 0; 
}; 

interface Derived1 : public Base 
{ 
    virtual void funcDerived1() = 0; 
}; 

interface Derived2 : public Base 
{ 
    virtual void funcDerived2() = 0; 
}; 

interface DDerived : public Derived1, public Derived2 
{ 
    virtual void funcDDerived() = 0; 
}; 

class Implementation : public DDerived 
{ 
public: 
    void funcBase() { cout << "base" << endl; } 
    void funcDerived1() { cout << "derived1" << endl; } 
    void funcDerived2() { cout << "derived2" << endl; } 
    void funcDDerived() { cout << "dderived" << endl; } 
}; 

int main() 
{ 
    DDerived *pObject = new Implementation; 
    pObject->funcBase(); 

    return 0; 
} 

Lý do tôi viết mã này là để kiểm tra nếu funcBase function() có thể được gọi trong một thể hiện của DDerived hoặc không phải. Trình biên dịch C++ của tôi (Visual Studio 2010) đã cho tôi một thông báo lỗi biên dịch khi tôi cố biên dịch mã này. Theo tôi, không có vấn đề gì trong mã này bởi vì chắc chắn rằng hàm funcBase() sẽ được thực hiện (do đó overriden) trong một số lớp dẫn xuất của giao diện DDerived, bởi vì nó là ảo thuần túy. Nói cách khác, bất kỳ biến con trỏ nào của loại Implementation * phải được liên kết với một cá thể của một lớp bắt nguồn từ Implentation và ghi đè hàm funcBase().

Câu hỏi của tôi là, tại sao trình biên dịch cung cấp cho tôi thông báo lỗi như vậy? Tại sao cú pháp C++ được định nghĩa như vậy; tức là, để coi trường hợp này là lỗi? Làm thế nào tôi có thể làm cho mã chạy? Tôi muốn cho phép thừa kế nhiều giao diện. Tất nhiên, nếu tôi sử dụng "công ảo" hoặc tái khai báo hàm funcBase() trong Implementation như

interface DDerived : public Derived1, public Derived2 
{ 
    virtual void funcBase() = 0; 
    virtual void funcDDerived() = 0; 
}; 

sau đó tất cả mọi thứ chạy không có vấn đề. Nhưng tôi không muốn làm điều đó và tìm kiếm phương pháp thuận tiện hơn, bởi vì thừa kế ảo có thể làm suy giảm hiệu suất, và việc khai báo lại quá tẻ nhạt để làm nếu quan hệ thừa kế của các lớp là rất phức tạp. Có phương pháp nào để cho phép đa thừa kế các giao diện trong C++ khác ngoài việc sử dụng thừa kế ảo không?

+0

Sức mạnh tâm linh của tôi cho tôi biết rằng lỗi này được gọi đến hàm lớp cơ sở không rõ ràng. –

+0

Nhưng nó không phải là một lớp cơ sở, nó hoặc là một giao diện cơ bản hoặc một cấu trúc cơ sở. –

+0

có thể trùng lặp của [C++ Multiple Inheritance - tại sao bạn không làm việc?] (Http://stackoverflow.com/questions/5864466/c-multiple-inheritance-why-you-no-work) –

Trả lời

3

Ngôn ngữ C++ được thiết kế theo cách mà trong phương pháp tiếp cận đầu tiên của bạn mà không có thừa kế ảo sẽ có hai bản sao chính của phương thức và không thể tìm ra phương thức nào cần gọi.

Thừa kế ảo là giải pháp C++ để kế thừa cùng một hàm từ nhiều cơ sở, vì vậy tôi khuyên bạn chỉ nên sử dụng phương pháp đó.

Cách khác bạn có được coi là không thừa hưởng cùng một hàm từ nhiều cơ sở? Bạn có thực sự có một lớp dẫn xuất mà bạn cần để có thể coi là Derived1 hoặc Derived2 HOẶC Base tùy thuộc vào ngữ cảnh?

Trong trường hợp này, việc xây dựng trên một vấn đề cụ thể hơn là một ví dụ giả tạo có thể giúp cung cấp thiết kế tốt hơn.

+1

Cho phép tôi không đồng ý. Trong phần lớn các trường hợp OP đăng một chương trình thực sự, mọi người bắt đầu hét lên rằng họ nên đăng SSCCE thay thế. Đây là một trong những trường hợp hiếm hoi mà OP thực hiện, và sau đó bạn phàn nàn về _that._ –

+0

@Mr Lister Bạn nói đúng! Thật không may trong trường hợp này tôi không có đủ ngữ cảnh (đầy đủ mã đơn giản hóa) để nói tại sao 'Base',' Derived', và 'Derived2' không phải tất cả các lớp trực giao được kế thừa trực tiếp trong' DDerived'. –

1
DDerived *pObject = new Implementation; 
pObject->funcBase(); 

Điều này tạo ra một loại con trỏ DDerived to a Implementation. Khi bạn đang sử dụng DDerived bạn thực sự chỉ có một con trỏ đến một giao diện.

DDerived không biết về việc triển khai funcBase vì sự mơ hồ của việc có funcBase được xác định trong cả Derived1 và Derived2.

Điều này đã tạo ra một viên kim cương kế thừa là những gì thực sự gây ra vấn đề.

http://en.wikipedia.org/wiki/Diamond_problem

Tôi cũng đã phải kiểm tra vào "từ khóa" giao diện bạn có ở đó

nó là một phần mở rộng ms-cụ thể đó là được công nhận bởi visual studio

7

Như bạn đã xác định nó, cấu trúc đối tượng của bạn trông giống như sau:

enter image description here

Điểm quan trọng ở đây là mỗi trường hợp của Implementation chứa hai trường hợp hoàn toàn riêng biệt là Base. Bạn đang cung cấp ghi đè Base::funcBase nhưng không biết liệu bạn có đang cố gắng ghi đè số funcBase cho số Base mà bạn được kế thừa qua Derived1 hoặc Base mà bạn được kế thừa qua Derived2 hay không.

Có, cách sạch sẽ để giải quyết vấn đề này là thừa kế ảo. Điều này sẽ thay đổi cấu trúc của bạn để chỉ có một thể hiện của cơ sở:

enter image description here

này là gần như chắc chắn những gì bạn thực sự muốn. Có, nó có một danh tiếng cho các vấn đề hiệu suất trong những ngày của trình biên dịch nguyên thủy và 25 MHz 486 và như vậy. Với trình biên dịch và trình xử lý hiện đại, bạn đang không thể gặp sự cố.

Một khả năng khác sẽ là một số loại thay thế dựa trên mẫu, nhưng có xu hướng tràn ngập phần còn lại của mã của bạn - tức là thay vì gửi Base *, bạn viết một mẫu sẽ hoạt động với bất kỳ thứ gì cung cấp hàm A, B, và C, sau đó vượt qua (tương đương) Implementation làm thông số mẫu.

+0

Cảm ơn bạn đã trả lời. Bạn nói rằng "nhưng nó không biết bạn đang cố gắng ghi đè lên funcBase cho Base bạn thừa hưởng thông qua Derived1, hoặc Base bạn kế thừa thông qua Derived2", nhưng tôi nghĩ rằng nó không phải là trường hợp; khi tôi thay thế "DDerived * pObject" thành "Implementation * pObject", trình biên dịch không phàn nàn nữa ... – user1361349

+0

Điều đó có thể vì hai bản sao của funcBase thực sự được ghi đè bởi cùng một phương thức được đưa ra trong Triển khai. Tương tự, như tôi đã nói trong câu hỏi của mình, khi tôi thêm "void funcBase() = 0" trong định nghĩa DDerived, không có lỗi biên dịch. Bạn nghĩ gì về điều này? Cảm ơn bạn. – user1361349

1

Tôi nghĩ C++ Standard 10.1.4 - 10.1.5 có thể giúp bạn hiểu vấn đề trong mã của mình.

class L { public: int next; /∗ ... ∗/ }; 
class A : public L { /∗...∗/ }; 
class B : public L { /∗...∗/ }; 
class C : public A, public B { void f(); /∗ ... ∗/ }; 

10.1.4 Một specifier lớp cơ sở mà không chứa từ khóa virtual, định một lớp cơ sở không ảo. Một thông số lớp cơ sở mà chứa từ khóa ảo, chỉ định một lớp cơ sở ảo. Đối với mỗi trường hợp xuất hiện riêng biệt của một lớp cơ sở phi ảo trong mạng lớp học của lớp được dẫn xuất nhiều nhất, đối tượng có nguồn gốc cao nhất (1.8) phải chứa một lớp con cơ sở riêng biệt tương ứng của loại đó. Đối với mỗi lớp cơ sở riêng biệt được chỉ định ảo, đối tượng được xuất phát nhiều nhất phải chứa một lớp con cấp cơ sở duy nhất của loại đó. [Ví dụ: cho đối tượng thuộc loại C, mỗi sự xuất hiện riêng biệt của lớp cơ sở (không ảo) L trong mạng lớp C tương ứng một đối một với một đối tượng con L riêng biệt trong đối tượng loại C. Với lớp C được xác định ở trên, đối tượng của lớp C sẽ có hai lớp con của lớp L như được hiển thị bên dưới.

10.1.5 Trong các mạng như vậy, trình độ rõ ràng có thể được sử dụng để xác định loại phụ đề nào là . Phần thân hàm C :: f có thể tham chiếu đến thành viên tiếp theo của mỗi L subobject: void C :: f() {A :: next = B :: next; } // cũng được hình thành. Nếu không có A hoặc B :: :: vòng loại, định nghĩa của C :: f trên sẽ là vô hình thành do sự nhập nhằng

Vì vậy, chỉ cần thêm vòng loại khi gọi pObject-> funcBase() hoặc giải quyết sự mơ hồ theo một cách khác.

pObject->Derived1::funcBase(); 

Cập nhật: đọc cũng rất hữu ích sẽ 10,3 Chức năng ảo của Standard.

Chúc một ngày cuối tuần vui vẻ :)

Các vấn đề liên quan