2009-06-09 33 views
21

在實現一個虛擬賦值運算符的時候,我以一個有趣的行爲結束了遊戲。這不是編譯器故障,因爲g ++ 4.1,4.3和VS 2005共享相同的行爲。爲什麼虛擬分配的行爲與同一個簽名的其他虛擬功能的行爲不同?

基本上,對於實際正在執行的代碼,虛擬運算符=的行爲與任何其他虛擬函數的行爲不同。

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 &) 
} 

的效果是,虛擬運營商=具有比具有相同簽名的任何其他虛擬功能的不同的行爲([0]與[1]),通過調用操作者的基本版本通過時稱爲真正的派生對象([1])或派生參考([3]),而在通過基準引用([2])調用時,它執行常規虛擬函數,或者左值或右值是基準引用,另一個派生參考([4],[5])。

這種奇怪的行爲是否有任何明智的解釋?

回答

13

這是怎麼一回事呢:

如果我改變[1]到

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

然後工作的事情你期望的方式。有一個在Derived自動生成的賦值運算符,看起來像這樣:

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; 
} 

在您的例子編譯器有足夠的信息來猜測abDerived型的,所以他們選擇使用自動生成的操作員上方調用你的。這就是你得到的結果[1]。我的指針投射迫使編譯器按照您的方式執行,因爲我告訴編譯器「忘記」b的類型爲Derived,因此它使用Base

其他結果可以用相同的方式解釋。

+3

有沒有猜測這裏涉及。規則非常嚴格。 – MSalters 2009-06-09 11:14:52

4

沒有爲Derived類定義的用戶提供的賦值運算符。因此,編譯器綜合了一個內部的基類賦值運算符,該屬性從Derived類的綜合賦值運算符中調用。

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

因此,a = b; // [1] outputs: Base::operator=(Base const &)

在派生類,基類賦值運算符已被覆蓋,因此,被覆蓋的方法獲取的派生類的虛擬表中的條目。當通過引用或指針調用該方法時,由於運行時的VTable條目解析度而被調用派生類重寫方法。

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

==>內部==>(對象 - >的VTable [Assignement操作]) 獲取賦值運算符中的VTable到對象所屬的類的條目和調用該方法。

3

如果您未能提供合適的operator=(即正確的返回值和參數類型),則編譯器將提供默認operator=,該值將重載用戶定義的值。在你的情況下,它會在複製Derived成員之前調用Base::operator= (Base const&)

檢查此link有關運算符=正虛擬的詳細信息。

5

有三個運營商=在這種情況下:

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

這就解釋了爲什麼它看起來像基地::運算符=(const的基礎&)被稱爲 「虛擬」 的情況下,[1]。它由編譯器生成的版本調用。這同樣適用於案例[3]。在情況2中,右側參數'bb'的類型爲Base &,因此不能調用Derived :: operator =(派生的&)。

相關問題