2011-01-26 92 views
6

在繼續閱讀本文之前,請首先閱讀Is there a difference in C++ between copy initialization and direct initialization?,確保你明白它在說什麼。C++複製初始化和直接初始化,奇怪的情況

我會在這裏先總結一下規則(讀取標準n3225 8.5/16,13.3.1.3,13.3.1.4和13.3.1.5),

1)直接初始化,所有構造函數都會被視爲重載設置,重載分辨率將根據重載解析規則選擇最佳分辨率。

2)對於複製初始化和源類型與目標類型相同或從目標類型派生,除了僅將轉換構造函數(構造函數沒有顯式)視爲重載集外,其規則與上述相同。這實際上意味着顯式複製/移動構造函數不會被視爲重載集合。 3)對於上面(2)中未包括的複製初始化情況(源類型與目標類型不同並且不是源自目標類型),我們首先考慮用戶定義的轉換序列,其可以從源類型轉換爲目標類型或(當使用轉換函數時)到其派生類。如果轉換成功,則結果用於直接初始化的目標對象。在本用戶定義的轉換序列中,根據8.5/16和13.3.1.4中的規則,將考慮轉換ctors(非顯式ctors)和非顯式轉換函數。

3.2)結果prvalue將直接初始化爲目標對象,如(1)中列出的規則,參見8.5/16。

好的,對規則來說足夠了,讓我們看看一些奇怪的代碼,我真的不知道我的推理是錯誤的,還是隻是所有的編譯器都是錯誤的。請幫助我,謝謝。

struct A 
{ 
    A (int) { } 
    A() { } 
    explicit A(const A&) { } 
}; 
struct B 
{ 
    operator A() { return 2; } 
    //1) visual c++ and clang passes this 
    //gcc 4.4.3 denies this, says no viable constructor available 
}; 
int main() 
{ 
    B b; 
    A a = b; 
    //2) oops, all compilers deny this 
} 

在我的理解,對於(1),

operator A() { return 2; } 

因爲C++具有函數返回被作爲複製初始化,根據上述規則的規則,2將被首先隱式轉換到A,這應該是確定的,因爲A有一個構造函數A(int)。然後,轉換後的臨時值將被用來直接初始化返回的對象,這應該也可以,因爲直接初始化可以使用顯式拷貝構造函數。所以GCC是錯誤的。

對於(2),

A a = b; 

在我的理解,首先b爲隱式轉換爲A,由運營商A(),然後將轉換後的值將用於直接初始化,這可當然要調用顯式拷貝構造函數?因此,這應該通過編譯和所有編譯器都是錯誤的?請注意,對於(2),visual C++和clang都有類似於 的錯誤「錯誤,無法從B轉換爲A」,但如果我在A的複製構造函數中刪除顯式關鍵字,則錯誤不見了..

感謝您的閱讀。


編輯1

因爲有人還是沒有得到我的意思,我引述8.5/16以下的標準,

否則(即,對剩餘的 複印通初始化情況), 用戶定義的轉換序列 可以從源類型轉換爲 目標類型或(當使用 轉換功能時)轉換爲 的派生類如13.3.1.4中所述列舉爲 ,並且最好的 通過分解(13.3)的過載 來選擇。如果轉換 無法完成或不明確,則 初始化格式不正確。使用 初始化程序表達式作爲其參數 調用所選的 函數;如果該函數是構造函數 ,則該調用將初始化 暫時的目標類型的cv未合格版本 版本。 臨時是一個prvalue。的 呼叫的結果(這是臨時用於 構造情況下)隨後被用於 直接初始化,根據上述規則 ,那是 目的地的 複製初始化的對象。在某些情況下, 的執行被允許爲 通過構造 直接初始化對象的 直接初始化來消除此 直接初始化中固有的複製;見 12.2,12.8。

請注意,它確實提到在用戶定義的轉換後直接初始化。這意味着,根據我的理解,以下代碼應遵守規則,正如我所評論的,這是由clang,coomeau online,visual C++所證實的,但GCC 4.4.3不能同時滿足(1)和(2)的要求。雖然這是一個奇怪的規則,但它遵循標準的推理。

struct A 
{ 
    A (int) { } 
    A() { } 
    explicit A(const A&) { } 
}; 

int main() 
{ 
    A a = 2; //1)OK, first convert, then direct-initialize 
    A a = (A)2; //2)oops, constructor explicit, not viable here! 
} 
+0

Comeau Online以書面形式接受整個代碼片段。 –

+0

好的,謝謝。這給了我一些信心,我的推理至少不會走向錯誤的方向。^_^ – user534498

+0

@詹姆斯麥克奈利斯:然而,科莫在線拒絕我的答案中的代碼,儘管根據OP的推理它應該被接受。 – AnT

回答

8

你宣佈你的拷貝構造函數explicit(順便說一句,爲什麼?),這意味着它不能再用於類對象的隱式拷貝。爲了使用這個構造函數進行復制,你現在被迫使用直接初始化語法。請參閱12.3.1/2

2顯式構造函數與非顯式構造函數一樣構造對象,但只在直接初始化語法(8.5)或轉換(5.2.9,5.4)用過的。

這個問題可以通過下面的更短的例子

struct A { 
    A() {} 
    explicit A(const A&) {} 
}; 

int main() { 
    A a; 
    A b = a; // ERROR: copy-initialization 
    A c(a); // OK: direct-initialization 
} 

這是您的轉換塊都來自工作,因爲它們都依賴於複製初始化,這又依賴於來說明隱式複製。並且你禁用隱式複製。

此外,請參閱Defect Report #152其中涵蓋此特定問題。雖然我不確定「提議的決議」的後果應該是什麼......

+0

您好,您沒有閱讀標準8.5/16中的規則。在複製初始化期間,直接初始化將在用戶定義的轉換後使用,當然這將允許使用顯式複製構造函數。 – user534498

+0

@ user534498:好的,12.3.1/2似乎需要直接初始化*語法*以使顯式構造函數可用。僅僅是直接初始化是不夠的,*語法*必須存在於代碼中。它不存在於你的。 – AnT

+0

嗨安德烈,請閱讀我的編輯1.順便說一句,我認爲在12.3.1/2中的例子並不矛盾我的上述推理,因爲它不涉及顯式拷貝構造函數。 – user534498

0

正如你所提到的,gcc不會編譯下面的代碼。

struct A { 
    A(int) {} 
    explicit A(A const&) {} 
}; 

int main() { 
    A a = 2; 
} 

我認爲這是不符合標準符合現行標準8.5 p15。
但是,對於你的第一個案例,

struct A { 
    A(int) {} 
    explicit A(A const&) {} 
}; 

struct B { 
    operator A() { return 2; } 
}; 

int main() { 
    B b; 
    A a = b; 
} 

我不相信,允許這是符合的。
如您所知,return將在概念上調用兩次複製。
return 2;隱含地從int 2構建A,並且它被用於 直接初始化時間返回值(R)。
但是,是否應該將直接初始化應用於從R到a的第二個複製 ?
我無法找到當前標準中明確指出 直接初始化應該應用兩次的措辭。
由於確定這個序列破壞了explicit規範的意義,即使編譯器開發人員認爲允許這個 是一個缺陷,我也不會感到驚訝。

讓我做一個不必要的補充。
編譯器的不一致行爲並不意味着編譯器直接具有 缺陷。
該標準已經有缺陷報告顯示的缺陷。
例如,C標準不允許從一個指針轉換爲 類型T的陣列,一個指針到的const數組T.
這是允許在C++中,我認爲它應該被允許在語義上類似於C 。
Gcc發出有關此轉換的警告。Comeau發出錯誤,並且不會編譯。