2008-11-14 33 views
12

Trong C++, có thể có một lớp cơ sở cộng với lớp dẫn xuất thực hiện một giao diện duy nhất không?C++: Nguồn gốc + Lớp cơ sở triển khai một giao diện đơn lẻ?

Ví dụ:

class Interface 
{ 
    public: 
     virtual void BaseFunction() = 0; 
     virtual void DerivedFunction() = 0; 
}; 

class Base 
{ 
    public: 
     virtual void BaseFunction(){} 
}; 

class Derived : public Base, public Interface 
{ 
    public: 
     void DerivedFunction(){} 
}; 

void main() 
{ 
    Derived derived; 
} 

này sai vì nguồn gốc không thể được khởi tạo. Theo như trình biên dịch có liên quan Giao diện :: BaseFunction không bao giờ được xác định.

Cho đến nay các giải pháp duy nhất mà tôi đã tìm thấy sẽ được tuyên bố một đường chuyền thông qua chức năng trong nguồn gốc

class Derived : public Base, public Interface 
{ 
    public: 
     void DerivedFunction(){} 
     void BaseFunction(){ Base::BaseFunction(); } 
}; 

Có giải pháp nào tốt hơn?


EDIT: Nếu vấn đề, đây là một vấn đề thế giới thực tôi đã sử dụng các hộp thoại MFC.

Tôi có một lớp hộp thoại (MyDialog cho phép nói) có nguồn gốc từ CDialog. Do các vấn đề phụ thuộc, tôi cần tạo một giao diện trừu tượng (MyDialogInterface). Lớp sử dụng MyDialogInterface cần sử dụng các phương thức cụ thể cho MyDialog, nhưng cũng cần gọi CDialog :: SetParent. Tôi chỉ giải quyết nó bằng cách tạo MyDialog :: SetParent và có nó đi qua để CDialog :: SetParent, nhưng đã tự hỏi nếu có một cách tốt hơn.

Trả lời

16

C++ không nhận thấy hàm được kế thừa từ Cơ sở đã triển khai BaseFunction: Hàm này phải được triển khai rõ ràng trong lớp bắt nguồn từ Interface. Thay đổi nó theo cách này:

class Interface 
{ 
    public: 
     virtual void BaseFunction() = 0; 
     virtual void DerivedFunction() = 0; 
}; 

class Base : public Interface 
{ 
    public: 
     virtual void BaseFunction(){} 
}; 

class Derived : public Base 
{ 
    public: 
     virtual void DerivedFunction(){} 
}; 

int main() 
{ 
    Derived derived; 
} 

Nếu bạn muốn để có thể nhận được ngay với chỉ thực hiện một trong số họ, chia Interface thành hai giao diện:

class DerivedInterface 
{ 
    public: 
     virtual void DerivedFunction() = 0; 
}; 

class BaseInterface 
{ 
    public: 
     virtual void BaseFunction() = 0; 
}; 

class Base : public BaseInterface 
{ 
    public: 
     virtual void BaseFunction(){} 
}; 

class Derived : public DerivedInterface 
{ 
    public: 
     virtual void DerivedFunction(){} 
}; 

class Both : public DerivedInterface, public Base { 
    public: 
     virtual void DerivedFunction(){} 
}; 

int main() 
{ 
    Derived derived; 
    Base base; 
    Both both; 
} 

Lưu ý: chính phải hoàn trả int
Lưu ý: thực hành tốt là giữ virtual trước các hàm thành viên trong nguồn gốc được ảo trong cơ sở, ngay cả khi nó không được yêu cầu nghiêm ngặt.

+0

Nếu bạn làm theo cách đó, thì bạn không cần khai báo cơ sở thứ hai. – Torlack

+0

Đó là những gì anh ấy viết. Tôi không muốn thay đổi nó. Ông có thể có lý do để di chuyển nó như vậy –

+0

tôi đã thực hiện một bình luận về điều đó. cảm ơn :) –

1

Vấn đề là với ví dụ của bạn, bạn có hai triển khai là Interface, số xuất phát từ Base và số điện thoại đến từ Derived. Điều này là do thiết kế bằng ngôn ngữ C++. Như đã được chỉ ra, chỉ cần xóa lớp cơ sở Interface về định nghĩa của Derived.

+0

um .. không .... Kiểm tra mã một lần nữa. Cơ sở không kế thừa từ Giao diện. (Giao diện có chức năng DerviceFunction không được triển khai trong Base) –

4

Dường như nó không hoàn toàn là trường hợp có nguồn gốc "là một", cho thấy rằng ngăn chặn có thể là một triển khai tốt hơn so với kế thừa.

Ngoài ra, các chức năng thành viên có nguồn gốc của bạn cũng phải được khắc phục như ảo.

Bạn có thể đặt thành viên chứa tham chiếu hoặc con trỏ thông minh nếu bạn muốn ẩn chi tiết triển khai.

1

Tôi đồng ý với câu trả lời của litb. Tuy nhiên, có một cơ hội ở đây để hiểu một cái gì đó về cách chức năng ảo và nhiều công việc thừa kế.

Khi một lớp có nhiều lớp cơ sở, nó có các vtables riêng biệt cho mỗi lớp cơ sở.Derived sẽ có một cấu trúc vtable trông như thế này:

Derived 
vtable: Interface 
    BaseFunction* 
    DerivedFunction* 
vtable: Base 
    BaseFunction* 

Ngoài ra, mỗi lớp cơ sở sẽ chỉ có thể nhìn thấy vtable riêng của mình. Khi Base được khởi tạo, nó sẽ điền vào con trỏ Base::BaseFunction trong vtable, nhưng không thể nhìn thấy vtable cho Giao diện.

Nếu mã mà bạn cung cấp có thể biên dịch, kết quả cấu trúc vtable của một thể hiện của Derived sẽ trông như thế này:

Derived 
vtable: Interface 
    BaseFunction* = 0 
    DerivedFunction* = Derived::DerivedFunction 
vtable: Base 
    BaseFunction* = Base::BaseFunction 
0

tôi thấy một điều thiếu từ câu trả lời litb của. Nếu tôi có phiên bản Derived, tôi có thể nhận được DerivedInterfaceBaseInterface. Nhưng nếu tôi chỉ có một số DerivedInterface Tôi không thể nhận được BaseInterface kể từ khi phát sinh DerivedInterface từ BaseInterface sẽ không hoạt động.

Nhưng, toàn bộ thời gian này tôi đã giới hạn bản thân mình để biên dịch kiểm tra thời gian vì một lý do nào đó. Điều này DerivedInterface chỉ hoạt động tuyệt vời:

class DerivedInterface 
{ 
    public: 
     virtual void DerivedFunction() = 0; 
     BaseInterface* GetBaseInterface() 
      {return dynamic_cast<BaseInterface*>(this);} 
}; 

void main() 
{ 
    Derived derived; 

    DerivedInterface* derivedInterface = &derived; 
    derivedInterface->GetBaseInterface()->BaseFunction(); 
} 

Không có chức năng vượt qua cần thiết trong Có nguồn gốc và mọi người đều hạnh phúc. Chắc chắn, nó không hoàn toàn là một giao diện nữa, nhưng đó là tốt. Tại sao tôi không nghĩ về điều đó sớm hơn? :)

+0

không sao cả. bạn chỉ cần tạo một lớp xuất phát từ DerivedInterface và BaseInterface, và sau đó bạn có chính xác giao diện của lớp "Giao diện" trong câu hỏi ban đầu của bạn –

+1

. không bao giờ sử dụng dynamic_cast như vậy :) dynamic_cast nên là một phương sách cuối cùng để làm việc xung quanh những gì người ta không thể làm bằng cách thiết kế giao diện chính xác. trong trường hợp của bạn, chỉ cần làm Giao diện như tôi đã nói trong bình luận ở trên, và sau đó bạn có thể làm BaseInterface * baseInterface = & derived; và gọi cả hai :) –

+0

Có, điều đó sẽ cho tôi cùng một giao diện, nhưng thực hiện khác nhau, đó là toàn bộ vấn đề. Tôi đã thực hiện cho một lớp cơ sở và có nguồn gốc, và tôi cần một giao diện duy nhất cho họ.Có vẻ như điều này gần như tôi sẽ nhận được. – Joe

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