2012-09-11 16 views
13

Tôi có đoạn mã này (contrived từ rắc rối thực tế cuộc sống của tôi)Sử dụng tham số kiểu phân lớp trong chức năng ảo

Nó không thể biên dịch, phàn nàn ExtendsB không thực hiện B::Run(A* a). Tuy nhiên, nó không có vấn đề tìm hiểu phần mở rộng để A* Run();

class A { }; 

class ExtendsA : public A { }; 

class B 
{ 
public: 
    virtual ~B(){} 
    virtual void Run(A* a) = 0; 
    virtual A* Run() = 0; 
}; 

class ExtendsB : public B 
{ 
public: 
    virtual ~ExtendsB(){} 

    // Not OK! It does not see it as an implementation of 
    // virtual void Run(A* a) = 0; 
    virtual void Run(ExtendsA* ea) {}; 
    virtual ExtendsA* Run() { return new ExtendsA(); }; // OK 
}; 

Tại sao C++ cho phép thay đổi kiểu trả về một sub-class, nhưng không phải là kiểu tham số?

Đây có phải là lý do hợp lý hoặc chỉ là điểm bị thiếu trong thông số ngôn ngữ?

Trả lời

14

Tại sao C++ cho phép thay đổi kiểu trả về một sub-class , nhưng không phải kiểu tham số? tiêu chuẩn

C++ cho phép bạn sử dụng một Covariant return type khi overidding chức năng ảo nhưng không cho phép bạn thay đổi chức năng parameters.And có có một lý do tốt đằng sau nó.

Lý do:

Trọng về cơ bản có nghĩa là một trong hai phương thức của lớp cơ sở hoặc phương pháp lớp Derived sẽ được gọi vào thời gian chạy tùy thuộc vào đối tượng thực tế được trỏ bởi con trỏ.
Nó ngụ ý rằng:
tức là: "Mọi trường hợp phương pháp lớp cơ sở có thể được gọi có thể được thay thế bằng cách gọi đến phương thức lớp gốc mà không cần thay đổi mã gọi."

Nếu quy tắc trên không đúng chỗ, nó sẽ để lại một cửa sổ để phá vỡ mã hiện tại bằng cách bổ sung chức năng mới (các lớp dẫn xuất mới).

Nếu bạn có mẫu thử hàm trong lớp dẫn xuất khác với tham số w.r.t của hàm lớp cơ sở thì hàm không ghi đè hàm lớp cơ sở, vì quy tắc trên bị hỏng. Tuy nhiên, các kiểu trả về Covariant không phá vỡ quy tắc này, bởi vì upcasting xảy ra một cách ngầm định và một lớp con Base luôn trỏ đến một đối tượng lớp dẫn xuất mà không cần bất kỳ phép đúc nào, do đó tiêu chuẩn thực thi điều kiện này của các kiểu trả về covariant trên các kiểu trả về.

+2

Đối với quy tắc đó, đối số không cần phải khớp 100%. Trong khi * đối số covariant * sẽ phá vỡ quy tắc đó, * đối số biến thể * sẽ không phá vỡ nó (mọi đối số có thể được chuyển tới cơ sở cũng có thể được chuyển đến kiểu bắt nguồn) và chúng cũng không được phép. Một phần của lý do là x-phương sai không đến miễn phí và phải được xử lý bởi trampoline/thunks mà sửa chữa các đối số trả về (trong trường hợp hiệp phương sai). Việc cho phép thay đổi contra trong các đối số rất lớn (theo cấp số nhân?) Tăng số lượng các hàm trampoline được yêu cầu và kích thước của bảng ảo. –

8

Lỗi này khá rõ ràng: Bạn cần void Run(A*) trong lớp học ExtendedB, nhưng bạn không có điều đó. Tất cả những gì bạn có là void Run(ExtendedA*), nhưng điều đó không giống nhau: Lớp cơ sở hứa hẹn sẽ chấp nhận bất kỳA -pointer nào, nhưng ExtendedB::Run của bạn là bộ chọn và chỉ chấp nhận một tập hợp con hẹp.

(Bạn đang bối rối hiệp phương sai và contravariance, nhưng đó không phải là thích hợp cho C++, vì C++ không cho phép ghi đè contravariant.)

3

Nó chỉ đơn giản là hai loại khác nhau, mà làm cho nó hai chức năng riêng biệt với hai chữ ký riêng biệt.

Nói chung, nếu bạn đang sử dụng trình biên dịch hiểu C++ 11, bạn nên sử dụng từ khóa ghi đè lên các hàm có ý định ghi đè lên một hàm khác. Trong trường hợp của bạn, lỗi trở nên rõ ràng vì lớp cơ sở trừu tượng, nhưng trong các trường hợp khác, lỗi này có thể gây ra nhiều lỗi ...

+0

+1 để đề cập đến 'ghi đè'. – xtofl

2

Chuyên loại trả về làm cho lớp con nghiêm ngặt hơn - nó sẽ hoạt động như một A. Tuy nhiên, hạn chế phương thức Run để chỉ chấp nhận một lớp con của đối số gốc làm cho B không hoạt động như một A.

Mô hình thừa kế là mối quan hệ. Mã của bạn vi phạm nguyên tắc thay thế Liskov.

+0

Vào năm 2012, C++ không được coi là ngôn ngữ "con trỏ OOP-by thuần túy" (điều này chỉ đúng đến năm 1998). Ngày nay, kế thừa C++ chỉ là một cơ chế tổng hợp mà CAN có thể phục vụ cho việc thay thế Liskov nhưng cũng có thể có các mục đích khác. Các lớp C++ không nhất thiết phải là các đối tượng OOP, do đó thừa kế không nhất thiết là một mối quan hệ "là-a": nó phụ thuộc vào ngữ cảnh. Một mô tả thích hợp hơn là "hoàn toàn giống như một" và không "là một". –

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