Có thể cho một lớp kế thừa triển khai một hàm ảo với kiểu trả về khác (không sử dụng mẫu làm trả về) không?
Câu trả lời:
Trong một số trường hợp, có, việc một lớp dẫn xuất ghi đè một hàm ảo bằng cách sử dụng kiểu trả về khác là hợp pháp, miễn là kiểu trả về đồng biến với kiểu trả về ban đầu. Ví dụ, hãy xem xét những điều sau:
class Base {
public:
virtual ~Base() {}
virtual Base* clone() const = 0;
};
class Derived: public Base {
public:
virtual Derived* clone() const {
return new Derived(*this);
}
};
Ở đây, Base
định nghĩa một hàm ảo thuần túy được gọi là clone
trả về a Base *
. Trong triển khai dẫn xuất, hàm ảo này được ghi đè bằng cách sử dụng kiểu trả về Derived *
. Mặc dù kiểu trả về không giống như trong cơ sở, điều này hoàn toàn an toàn vì bất kỳ lúc nào bạn cũng có thể viết
Base* ptr = /* ... */
Base* clone = ptr->clone();
Lời gọi tới clone()
sẽ luôn trả về một con trỏ đến một Base
đối tượng, vì ngay cả khi nó trả về a Derived*
, con trỏ này hoàn toàn có thể chuyển đổi thành a Base*
và hoạt động được xác định rõ ràng.
Nói chung, kiểu trả về của một hàm không bao giờ được coi là một phần của chữ ký của nó. Bạn có thể ghi đè một hàm thành viên bằng bất kỳ kiểu trả về nào miễn là kiểu trả về là hiệp phương sai.
Base*
bằng long
và Derived*
với int
(hoặc ngược lại, không quan trọng). Nó sẽ không hoạt động.
Đúng. Các kiểu trả về được phép khác nhau miễn là chúng cùng biến . Tiêu chuẩn C ++ mô tả nó như thế này (§10.3 / 5):
Kiểu trả về của hàm ghi đè phải giống với kiểu trả về của hàm ghi đè hoặc đồng biến với các lớp của hàm. Nếu một hàm
D::f
ghi đè một hàmB::f
, kiểu trả về của các hàm là hiệp phương sai nếu thỏa mãn các tiêu chí sau:
- cả hai đều là con trỏ đến các lớp hoặc tham chiếu đến các lớp 98)
- lớp trong kiểu trả về
B::f
là cùng lớp với lớp trong kiểu trả vềD::f
hoặc, là lớp cơ sở trực tiếp hoặc gián tiếp rõ ràng của lớp trong kiểu trả vềD::f
và có thể truy cập được trongD
- cả con trỏ hoặc tham chiếu đều có cùng chứng chỉ cv và loại lớp trong kiểu trả về
D::f
có cùng chứng chỉ cv như hoặc ít hơn chứng chỉ cv so với loại lớp trong kiểu trả vềB::f
.
Chú thích chân trang 98 chỉ ra rằng "con trỏ nhiều cấp tới các lớp hoặc tham chiếu đến con trỏ nhiều cấp tới các lớp không được phép."
Tóm lại, if D
là một kiểu con của B
, thì kiểu trả về của hàm trong D
cần phải là kiểu con của kiểu trả về của hàm trong B
. Ví dụ phổ biến nhất là khi các kiểu trả về dựa trên D
và B
, nhưng chúng không nhất thiết phải như vậy. Hãy xem xét điều này, nơi chúng tôi có hai cấu trúc phân cấp loại riêng biệt:
struct Base { /* ... */ };
struct Derived: public Base { /* ... */ };
struct B {
virtual Base* func() { return new Base; }
virtual ~B() { }
};
struct D: public B {
Derived* func() { return new Derived; }
};
int main() {
B* b = new D;
Base* base = b->func();
delete base;
delete b;
}
Lý do điều này hoạt động là vì bất kỳ người gọi nào func
đang mong đợi một Base
con trỏ. Bất kỳ Base
con trỏ sẽ làm. Vì vậy, nếu các D::func
lời hứa luôn trả về một Derived
con trỏ, thì nó sẽ luôn thỏa mãn hợp đồng do lớp tổ tiên đặt ra vì bất kỳ Derived
con trỏ nào cũng có thể được chuyển đổi ngầm thành một Base
con trỏ. Như vậy, người gọi sẽ luôn nhận được những gì họ mong đợi.
Ngoài việc cho phép kiểu trả về thay đổi, một số ngôn ngữ cũng cho phép kiểu tham số của hàm ghi đè cũng khác nhau. Khi họ làm điều đó, họ thường cần phải được contravariant . Nghĩa là, nếu B::f
chấp nhận a Derived*
, thì D::f
sẽ được phép chấp nhận a Base*
. Con cháu được phép nới lỏng hơn trong những gì họ sẽ chấp nhận và chặt chẽ hơn trong những gì họ trả lại. C ++ không cho phép đối nghịch kiểu tham số. Nếu bạn thay đổi các kiểu tham số, C ++ sẽ coi đó là một hàm hoàn toàn mới, vì vậy bạn bắt đầu gặp phải tình trạng quá tải và ẩn. Để biết thêm về chủ đề này, hãy xem Hiệp phương sai và phương sai (khoa học máy tính) trong Wikipedia.
Việc triển khai lớp dẫn xuất của hàm ảo có thể có Kiểu trả về cùng phương .