Kiểu trả về hàm ảo C ++


Câu trả lời:


86

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à clonetrả 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.


9
Điều này "Bạn có thể ghi đè một hàm thành viên với bất kỳ kiểu trả về nào" là không đúng. Bạn có thể ghi đè miễn là kiểu trả về giống hệt hoặc hiệp phương sai (mà bạn đã giải thích), dấu chấm. Không có trường hợp chung chung nào ở đây.
bronekk

1
@ bronekk- Phần còn lại của câu mà bạn trích dẫn sau đó nói rằng kiểu trả về mới phải có thể sử dụng được ở bất kỳ nơi nào mà kiểu ban đầu sẽ là; nghĩa là, kiểu mới đồng biến với kiểu gốc.
templatetypedef,

phần còn lại của câu không đúng; tưởng tượng thay thế Base*bằng longDerived*với int(hoặc ngược lại, không quan trọng). Nó sẽ không hoạt động.
bronekk

@ bronekk- À vâng, tôi không nghĩ ra điều đó! Cảm ơn vì đã chỉ ra điều đó.
templatetypedef

1
Kiểu đó có thể sử dụng được ở bất cứ đâu mà kiểu gốc sẽ là và hiệp phương sai là hai ngữ cảnh khác nhau. Đồng biến có nghĩa là mối quan hệ giữa các kiểu thực hiện chức năng và mối quan hệ giữa các kiểu trả về thay đổi theo cùng một cách. Tương phản (mặc dù không hữu ích trong C ++) là bối cảnh ngược lại. Một số ngôn ngữ cho phép các đối số trái ngược nhau trong điều phối động (nếu cơ sở nhận một đối tượng thuộc kiểu T, thì kiểu dẫn xuất có thể nhận T 'trong đó T' là cơ sở của T - khi bạn đi xuống một thứ bậc, bạn di chuyển lên thứ kia ).
David Rodríguez - dribeas

53

Đú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::fghi đè một hàm B::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::flà cùng lớp với lớp trong kiểu trả về D::fhoặ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::fvà 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::fcó 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 Dlà một kiểu con của B, thì kiểu trả về của hàm trong Dcầ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 DB, 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 Basecon trỏ. Bất kỳ Basecon trỏ sẽ làm. Vì vậy, nếu các D::funclời hứa luôn trả về một Derivedcon 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ỳ Derivedcon trỏ nào cũng có thể được chuyển đổi ngầm thành một Basecon 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::fchấp nhận a Derived*, thì D::fsẽ đượ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.


2
Đây là một tính năng thực tế hay một tác dụng phụ từ kiểu trả về không được sử dụng trong độ phân giải?
Martin York

1
@Martin, chắc chắn là một tính năng. Tôi khá chắc chắn rằng giải quyết quá tải không liên quan gì đến nó. Kiểu trả về được sử dụng nếu bạn đang ghi đè một hàm.
Rob Kennedy

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.