2011-05-28 131 views
8

我一直認爲我很瞭解C++,但有時甚至會對最基本的東西感到驚訝。構造函數混淆

在下列情況下,我很困惑,爲什麼構造Derived::Derived(const Base&)被調用:

class Base 
{ }; 

class Derived : public Base 
{ 
    public: 

    Derived() { } 

    Derived(const Base& b) 
    { 
     std::cout << "Called Derived::Derived(const Base& b)" << std::endl; 
    } 
}; 

int main() 
{ 
    Derived d; 
    Base b; 
    d = b; 
} 

此輸出:Called Derived::Derived(const Base& b),表明在Derived第二構造函數被調用。現在,我認爲我很瞭解C++,但我無法弄清楚爲什麼會調用這個構造函數。我理解了整個「四規則」概念,並且我認爲表達式d = b可以做以下兩件事之一:或者1)調用隱式(編譯器生成的)賦值運算符Base,或者2)觸發編譯器錯誤抱怨功能Derived& operator = (const Base&)不存在。

取而代之,它稱爲構造函數,即使表達式d = b是一個賦值表達式。

那麼爲什麼會發生這種情況呢?

+0

也許增加一個複製賦值操作符和一個記錄語句。 ;) – Xeo 2011-05-28 17:38:46

+0

您選擇的答案忘了提及這一點,因爲您定義了Derived(const Base&)構造函數,並且您正在做一個downcast ...如果您沒有定義這個construtor,那麼在嘗試完成作業時會收到編譯錯誤正如我的回答所述。 – Hazok 2011-05-28 17:59:03

+0

@Zach:OP是她挑選的人,不用擔心她已收到所有答案的通知(並且會繼續收到通知)。如果你的答案很好,無論如何都會得到認真的讀者的支持,並且很快會加入最佳答案,並且對將來(不太小心)的讀者也是可見的:) – 2011-05-28 18:08:31

回答

11

d = b可能發生,因爲b轉換爲Derived。 第二個構造函數用於自動類型轉換。 這就像d =(衍生)b

派生isa基地,但基地不派生,所以它必須轉換之前轉讓。

+0

我沒有說它是一個拷貝構造函數。它是一個構造函數,用於創建從基礎派生。與你在劇組中想要的一樣。 – 2011-05-28 17:38:48

+0

忽略早先的評論,我忘記了適用的隱式轉換規則和序列。 – Xeo 2011-05-28 17:40:04

6

指派基地派生?也許你的意思是(a)由ref(b)或派生到基地。這沒有任何意義,但編譯器正確使用(非顯式)構造函數將Base實例轉換爲新的Derived實例(隨後將其分配到d中)。

使用一個explicut構造函數來防止它自動發生。

我個人認爲你搞砸了你的代碼示例,因爲,通常分配的Firstclass基地派生沒有任何意義,而無需轉換

+0

+1爲'顯式'的建議來真正解決問題。所有構造函數都應聲明爲「顯式」,除非有充分理由不要(如果需要隱式轉換)。 – 2011-05-28 17:39:43

+0

所有的構造函數不一定要明確聲明,特別是在空構造函數的情況下。整個行業的許多編碼標準都指定使用默認構造函數而不是空構造函數。 – Hazok 2011-05-28 17:45:06

+0

@Zach:現在一直在竊聽我一段時間......我不明白你的意思是什麼* empty *構造函數?我會認爲你的意思是默認的構造函數(即空的參數列表),但既然你直接反對這兩個術語,我不明白你的意思:/ – 2011-05-28 19:30:29

1

它不能分配不同類型的值,所以應該先構造一個Derived臨時。

2

由於您已經爲Derived定義了一個構造函數,它需要類型Base,並且您正在向下投射Base,因此編譯器會爲upcast選擇最合適的構造函數,在本例中爲Dervied(const Base & b)you已經定義。如果你沒有定義這個構造函數,那麼當你嘗試做這個任務時,你實際上會得到一個編譯錯誤。欲瞭解更多信息,你可以閱讀以下Linuxtopia

5

有在這裏打球兩個相互作用的特點:

  • 賦值運算符永遠不會繼承
  • 構造函數是不明確的,或者轉換操作符(operator T())定義用戶轉換,可用於隱式地爲轉換序列的一部分

Assignement算從不繼承

一個簡單的代碼例如:

struct Base {}; // implicitly declares operator=(Base const&); 
struct Derived: Base {}; // implicitly declares operator=(Derived const&); 

int main() { 
    Derived d; 
    Base b; 
    d = b; // fails 
} 

ideone

prog.cpp: In function ‘int main()’: 
prog.cpp:7: error: no match for ‘operator=’ in ‘d = b’ 
prog.cpp:2: note: candidates are: Derived& Derived::operator=(const Derived&) 

轉換序列

每當有一個 「阻抗」 錯配,如這裏:

  • Derived::operator=需要一個
  • 一個Base&提供

Derived const&參數,編譯器將嘗試建立一個轉換序列,以縮小差距。這種轉換序列可能包含在大多數一個用戶定義的轉換。

在這裏,它會尋找:

  • 可以與Base&(不明確)被調用的Derived任何構造
  • Base轉換操作符,將產生一個Derived項目

沒有Base::operator Derived(),但有一個Derived::Derived(Base const&)構造函數。

因此我們的轉換序列爲我們定義:

  • Base&
  • Base const&(簡單)
  • Derived(使用Derived::Derived(Base const&)
  • Derived const&(綁定到const引用臨時對象)

A然後調用Derived::operator(Derived const&)

在行動

如果我們增加了一些更多的痕跡,我們可以see it in action的代碼。

#include <iostream> 

struct Base {}; // implicitly declares Base& operator(Base const&); 
struct Derived: Base { 
    Derived() {} 
    Derived(Base const&) { std::cout << "Derived::Derived(Base const&)\n"; } 
    Derived& operator=(Derived const&) { 
    std::cout << "Derived::operator=(Derived const&)\n"; 
    return *this; 
    } 
}; 

int main() { 
    Derived d; 
    Base b; 
    d = b; 
} 

,輸出:

Derived::Derived(Base const&) 
Derived::operator=(Derived const&) 

注意:防止出現這種情況?

在C++中,可能會刪除一個用於轉換序列的構造函數。爲此,需要使用explicit關鍵字在構造函數的聲明前綴。

在C++ 0x中,也可以在轉換運算符(operator T())上使用此關鍵字。

如果這裏我們在Derived::Derived(Base const&)之前使用explicit那麼代碼就會變形,應該被編譯器拒絕。