2009-06-09 33 views
21

Trong khi chơi với việc triển khai toán tử gán ảo, tôi đã kết thúc bằng hành vi vui nhộn. Nó không phải là một trục trặc của trình biên dịch, vì g ++ 4.1, 4.3 và VS 2005 chia sẻ cùng một hành vi.Tại sao phân bổ ảo hoạt động khác với các chức năng ảo khác của cùng một chữ ký?

Về cơ bản, toán tử ảo = hoạt động khác với bất kỳ hàm ảo nào khác đối với mã thực sự đang được thực thi.

struct Base { 
    virtual Base& f(Base const &) { 
     std::cout << "Base::f(Base const &)" << std::endl; 
     return *this; 
    } 
    virtual Base& operator=(Base const &) { 
     std::cout << "Base::operator=(Base const &)" << std::endl; 
     return *this; 
    } 
}; 
struct Derived : public Base { 
    virtual Base& f(Base const &) { 
     std::cout << "Derived::f(Base const &)" << std::endl; 
     return *this; 
    } 
    virtual Base& operator=(Base const &) { 
     std::cout << "Derived::operator=(Base const &)" << std::endl; 
     return *this; 
    } 
}; 
int main() { 
    Derived a, b; 

    a.f(b); // [0] outputs: Derived::f(Base const &) (expected result) 
    a = b; // [1] outputs: Base::operator=(Base const &) 

    Base & ba = a; 
    Base & bb = b; 
    ba = bb; // [2] outputs: Derived::operator=(Base const &) 

    Derived & da = a; 
    Derived & db = b; 
    da = db; // [3] outputs: Base::operator=(Base const &) 

    ba = da; // [4] outputs: Derived::operator=(Base const &) 
    da = ba; // [5] outputs: Derived::operator=(Base const &) 
} 

Hiệu quả là các nhà điều hành ảo = có hành vi khác nhau hơn bất kỳ chức năng ảo khác với cùng một chữ ký ([0] so với [1]), bằng cách gọi phiên bản cơ sở của người điều khiển khi gọi qua Các đối tượng thực thụ ([1]) hoặc các tham chiếu có nguồn gốc ([3]) trong khi nó thực hiện như một hàm ảo thông thường khi được gọi thông qua các tham chiếu cơ sở ([2]), hoặc khi giá trị lvalue hoặc rvalue là tham chiếu cơ sở và giá trị khác Tham chiếu có nguồn gốc ([4], [5]).

Có giải thích hợp lý nào cho hành vi kỳ quặc này không?

Trả lời

13

Sau đây là cách nó đi:

Nếu tôi thay đổi [1] để

a = *((Base*)&b); 

sau đó mọi thứ làm việc theo cách bạn mong đợi. Có một toán tử gán tự động tạo ra trong Derived trông như thế này:

Derived& operator=(Derived const & that) { 
    Base::operator=(that); 
    // rewrite all Derived members by using their assignment operator, for example 
    foo = that.foo; 
    bar = that.bar; 
    return *this; 
} 

Trong ví dụ trình biên dịch của bạn có đủ thông tin để đoán rằng ab là loại Derived và vì vậy họ chọn để sử dụng các nhà điều hành sẽ tự động được tạo ra ở trên mà các cuộc gọi của bạn. Đó là cách bạn nhận được [1]. Con trỏ của tôi đúc lực lượng trình biên dịch để làm điều đó theo cách của bạn, bởi vì tôi nói với trình biên dịch để "quên" rằng b là loại Derived và vì vậy nó sử dụng Base.

Các kết quả khác có thể được giải thích theo cách tương tự.

+3

Không có sự đoán mò nào liên quan ở đây. Các quy tắc rất nghiêm ngặt. – MSalters

+0

Cảm ơn, Câu trả lời thực sự (như được đăng bởi ba người) là trình biên dịch tạo ra toán tử = cho lớp Derived ngầm gọi cho toán tử Base :: =. Tôi đánh dấu đây là 'câu trả lời được chấp nhận' vì đó là câu trả lời đầu tiên. –

+0

'a = static_cast (b);' sẽ là một cách để tránh phôi kiểu C (mang theo nguy cơ vô tình làm một diễn viên diễn giải lại) –

4

Không có toán tử gán do người dùng cung cấp được xác định cho lớp Có nguồn gốc. Do đó, trình biên dịch tổng hợp một và toán tử gán lớp cơ sở nội bộ được gọi từ toán tử gán tổng hợp đó cho lớp Derived.

virtual Base& operator=(Base const &) //is not assignment operator for Derived 

Do đó, a = b; // [1] outputs: Base::operator=(Base const &)

Trong lớp Derived, toán tử gán lớp cơ bản đã được ghi đè và do đó, phương pháp ghi đè được một mục trong bảng ảo của lớp Derived. Khi phương thức được gọi thông qua tham chiếu hoặc con trỏ thì phương thức ghi đè lớp bắt nguồn được gọi là do độ phân giải đầu vào VTable tại thời gian chạy.

ba = bb; // [2] outputs: Derived::operator=(Base const &) 

==> nội ==> (Object-> vtable [assignement điều hành]) Lấy mục nhập cho toán tử gán trong vtable của lớp mà đối tượng thuộc và gọi phương pháp này.

3

Nếu bạn không cung cấp operator= thích hợp (nghĩa là trả lại và loại đối số chính xác), mặc định operator= được trình biên dịch cung cấp quá tải bất kỳ người dùng nào do người dùng xác định. Trong trường hợp của bạn, nó sẽ gọi Base::operator= (Base const&) trước khi sao chép các thành viên có nguồn gốc.

Kiểm tra điều này link để biết chi tiết về toán tử = được tạo ảo.

5

Có ba operator = trong trường hợp này:

Base::operator=(Base const&) // virtual 
Derived::operator=(Base const&) // virtual 
Derived::operator=(Derived const&) // Compiler generated, calls Base::operator=(Base const&) directly 

Điều này giải thích lý do tại sao nó trông giống như cơ sở :: operator = (Base const &) được gọi là "hầu" trong trường hợp [1]. Nó được gọi từ phiên bản do trình biên dịch tạo ra. Điều tương tự cũng áp dụng cho trường hợp [3]. Trong trường hợp 2, đối số bên phải 'bb' có loại Base &, vì vậy Derived :: operator = (Derived &) không thể được gọi.

2

Lý do là có trình biên dịch cung cấp gán mặc định operator=. Được gọi trong trường hợp a = b và như chúng ta biết mặc định nội bộ gọi toán tử gán cơ sở.

Có thể tìm thêm giải thích về phân bổ ảo tại: https://stackoverflow.com/a/26906275/3235055

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