2010-04-17 41 views
7
#include <iostream> 
using namespace std; 

class A    { public: void eat(){ cout<<"A";} }; 
class B: public A { public: void eat(){ cout<<"B";} }; 
class C: public A { public: void eat(){ cout<<"C";} }; 
class D: public B,C { public: void eat(){ cout<<"D";} }; 

int main(){ 
    A *a = new D(); 
    a->eat(); 
} 

Tôi không chắc điều này được gọi là vấn đề kim cương hay không, nhưng tại sao nó không hoạt động?Tại sao có sự mơ hồ trong mẫu kim cương này?

Tôi đã hủy bỏ số eat() cho D. Vì vậy, nó không cần sử dụng bản sao của B hoặc C (vì vậy, sẽ không có vấn đề gì).

Khi tôi đã nói, a->eat() (nhớ eat() không phải là ảo), chỉ có một khả năng eat() để gọi, đó là A.

Tại sao sau đó, tôi nhận được lỗi này:

'A' is an ambiguous base of 'D'


chính xác không A *a = new D(); ý nghĩa gì với trình biên dịch ??

Tại sao cùng một vấn đề không xảy ra khi tôi sử dụng D *d = new D();?

+0

http://www.parashift.com/c++-faq-lite/multiple-inheritance.html#faq-25.9 – JRL

Trả lời

2

Hãy tưởng tượng một kịch bản hơi khác nhau

class A    { protected: int a; public: void eat(){ a++; cout<<a;} }; 
class B: public A { public: void eat(){ cout<<a;} }; 
class C: public A { public: void eat(){ cout<<a;} }; 
class D: public B,C { public: void eat(){ cout<<"D";} }; 

int main(){ 
    A *a = new D(); 
    a->eat(); 
} 

Nếu đây sẽ làm việc, nó sẽ tăng các a trong B hoặc a trong C? Đó là lý do tại sao nó mơ hồ.Con trỏ this và bất kỳ thành phần dữ liệu không tĩnh nào là khác biệt cho hai đối tượng phụ A (một trong số đó được chứa bởi phụ đề B và cấp còn lại là phụ đề C). Hãy thử thay đổi mã của bạn như thế này và nó sẽ làm việc (ở chỗ nó biên dịch và in "A")

class A    { public: void eat(){ cout<<"A";} }; 
class B: public A { public: void eat(){ cout<<"B";} }; 
class C: public A { public: void eat(){ cout<<"C";} }; 
class D: public B, public C { public: void eat(){ cout<<"D";} }; 

int main(){ 
    A *a = static_cast<B*>(new D()); 
     // A *a = static_cast<C*>(new D()); 
    a->eat(); 
} 

Đó sẽ gọi eat trên A subobject của BC tương ứng.

+0

@Johannes Schaub - litb: được rồi, tôi hiểu những gì bạn nói. Nhưng tại sao cùng một vấn đề không xảy ra khi tôi sử dụng 'D * d = new D(); ' – Moeb

+1

@cambr, tra cứu tên dừng lại khi bạn thực hiện' d-> eat() 'trong phạm vi' D' vì nó tìm thấy 'eat' trong' D'. Nó sẽ không chạm vào 'eat' trong' A', 'B' hoặc' C' và sẽ không cố gắng gọi chúng. Vì vậy, không có sự chuyển đổi sang 'A' được thực hiện trong trường hợp đó, và do đó không có sự mơ hồ nào nảy sinh. –

+0

Nếu bạn cố gắng 'd-> A :: eat()' bằng cách cố gắng gọi 'eat' trên' A', bạn sẽ gặp lại vấn đề tương tự vì nó sẽ cố chuyển đổi 'D *' xuống thành 'A ' * '. –

6

Kết quả kim cương trong hai trường hợp của A trong đối tượng D, và nó là mơ hồ mà một trong những bạn đang đề cập đến - bạn cần phải sử dụng thừa kế ảo để giải quyết việc này:

class B: virtual public A { public: void eat(){ cout<<"B";} }; 
class C: virtual public A { public: void eat(){ cout<<"C";} }; 

giả định rằng bạn thực sự chỉ muốn một ví dụ. Tôi cũng giả sử bạn thực sự có nghĩa là:

class D: public B, public C { public: void eat(){ cout<<"D";} }; 
+0

@Neil Butterworth: "bản sao của' A' trong đối tượng cuối cùng" Tại sao Tôi sẽ sao chép A trong đối tượng cuối cùng? – Moeb

+0

@cambr Tôi đã sử dụng "copy" để có nghĩa là "instance" - tôi đã thay đổi nó. –

+0

@Neil Butterworth: Ngoài ra, tôi đã định nghĩa 'eat()' một cách rõ ràng trong D. Vì vậy, nó không cần phải sử dụng các bản sao/thể hiện từ 'B' hoặc' C'. – Moeb

0

Lỗi bạn đang nhận không đến từ việc gọi eat() - lỗi đến từ dòng trước. Đó là bản thân upcast tạo ra sự mơ hồ. Như Neil Butterworth chỉ ra, có hai bản sao của A trong số D của bạn và trình biên dịch không biết bạn muốn a trỏ đến điểm nào.

+0

@Mike: tại sao tôi có 'bản sao A trong D' của bạn. Tôi đã xác định một 'ăn()' riêng biệt cho D. – Moeb

+0

Nó không liên quan gì đến 'ăn()'; cuộc gọi 'a-> eat()' không tự quan tâm đến những gì các lớp được bắt nguồn từ 'A' (như bạn nói,' eat' là không ảo) - nó sẽ luôn gọi là 'A :: eat'. Như @Neil nói, hãy xóa cuộc gọi đến 'eat' và bạn vẫn gặp phải vấn đề tương tự. –

2

Lưu ý rằng lỗi biên dịch là trên "A * a = new D();" dòng, không phải trên cuộc gọi đến "ăn".

Vấn đề là vì bạn đã sử dụng thừa kế không ảo, bạn kết thúc với lớp A hai lần: một lần đến B, và một lần qua C. Nếu ví dụ bạn thêm thành viên m vào A, thì D có hai người trong số họ : B :: m, và C :: m.

Đôi khi, bạn thực sự muốn có A hai lần trong biểu đồ đạo hàm, trong trường hợp này, bạn luôn cần phải chỉ ra bạn đang nói về cái nào. Trong D, bạn có thể tham khảo B :: m và C :: m riêng biệt.

Thỉnh thoảng, bạn thực sự chỉ muốn một A, trong trường hợp này bạn cần sử dụng virtual inheritance.

2

Đối với một tình huống thực sự bất thường, câu trả lời của Neil thực sự là sai (ít nhất một phần).

Với out thừa kế ảo, bạn nhận được hai bản sao riêng biệt A trong đối tượng cuối cùng.

"Viên kim cương" Kết quả trong một bản duy nhất của A trong đối tượng cuối cùng, và được sản xuất bởi sử dụng thừa kế ảo:

alt text

Kể từ khi "viên kim cương" có nghĩa là chỉ có một bản sao của A trong đối tượng cuối cùng, một tham chiếu đến A không tạo ra sự mơ hồ. Nếu không có thừa kế ảo, tham chiếu đến A có thể tham chiếu đến một trong hai đối tượng khác nhau (một ở bên trái hoặc một đối tượng bên phải trong biểu đồ).

+0

Bạn đã vẽ một biểu đồ đối tượng - câu trả lời "sai" của tôi đã đề cập đến biểu đồ kế thừa, đó là một viên kim cương. –

+0

@Jerry Quan tài: Tại sao cùng một vấn đề không xảy ra khi tôi sử dụng 'D * d = new D();'? Trong trường hợp đó, một đối tượng kiểu 'D' đang được tạo và các vấn đề tương tự sẽ xảy ra. – Moeb

+0

@cambr: vì 'A * a = new D();' không thể quyết định đối tượng phụ 'A' nào trỏ tới. Với 'D * d = new D();', nó sẽ trỏ đến đối tượng 'D' không phải là một trong hai đối tượng' A', vì vậy không có câu hỏi về điểm cần phải chỉ ra. –

0

Bạn muốn: (Achievable với thừa kế ảo)

  D
 /\
B   C
  \ /
  A

Và không: (Chuyện gì xảy ra mà không thừa kế ảo)

    D
  /  \
  B   C
  |     |
  A   A

ảo thừa kế có nghĩa là sẽ chỉ có 1 ví dụ của lớp cơ sở A không phải là 2.

loại D sẽ có 2 con trỏ vtable (bạn có thể nhìn thấy chúng trong sơ đồ đầu tiên), một cho B và một cho C người hầu như kế thừa A. Kích thước đối tượng của D được tăng lên vì nó lưu trữ 2 con trỏ ngay bây giờ; tuy nhiên hiện tại chỉ có một A.

Vì vậy, B::AC::A giống nhau và do đó không thể có các cuộc gọi không rõ ràng từ D. Nếu bạn không sử dụng thừa kế ảo, bạn có sơ đồ thứ hai ở trên. Và bất kỳ cuộc gọi đến một thành viên của A sau đó trở nên mơ hồ và bạn cần phải xác định đường dẫn mà bạn muốn thực hiện.

Wikipedia has another good rundown and example here

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