2011-03-29 96 views
4

考慮下面的代碼:爲什麼這裏需要複製構造函數?

struct S 
{ 
    S() {} 
    void f(); 
private: 
    S(const S&); 
}; 

int main() 
{ 
    bool some_condition; 
    S my_other_S; 
    (some_condition ? S() : my_other_S).f(); 
    return 0; 
} 

GCC編譯失敗這一點,他說:

test.cpp: In function 'int main()': 
test.cpp:6:5: error: 'S::S(const S&)' is private 
test.cpp:13:29: error: within this context 

我不明白爲什麼拷貝構造應在該行正發生 - 的意圖是簡單地呼籲f()上有一個默認構造的S實例,或my_other_S,也就是說,它應該等同於:

if (some_condition) 
    S().f(); 
else 
    my_other_S.f(); 

第一種情況和爲什麼需要複製構造函數有什麼不同?

EDIT:是否有任何方式,然後,在此表示「上執行此操作或者預先存在的對象上的臨時」在表達式上下文?

+0

是不是if語句做你想要什麼?在單個表達式中做所有事情的優點是什麼(混淆除外)? – 2011-03-30 07:38:43

+0

@Bo if語句需要重複'f()'部分,這部分實際上可能是一個帶有許多參數的函數,每個參數都可能是冗長的表達式的結果......您得到的想法 – HighCommander4 2011-03-30 19:37:34

+0

@Bo此外,在某些上下文可能無法將表達式重寫爲if語句 - 例如,如果該表達式在初始化列表中用作實例變量的初始值設定項。 – HighCommander4 2011-03-30 20:08:08

回答

8

?:結果是一個rvalue,一個新的對象時,如果其中一個參數是一個rvalue。爲了創建這個右值,編譯器必須複製任何結果。

if (some_condition) 
    S().f(); // Compiler knows that it's rvalue 
else 
    my_other_S.f(); // Compiler knows that it's lvalue 

這是你不能做

struct B { private: B(const B&); }; 
struct C { C(B&); C(const B&); }; 
int main() { 
    B b; 
    C c(some_condition ? b : B()); 
} 

我改變了我的例子,因爲舊得有點鬧心一樣的道理。你可以清楚地看到這裏沒有辦法編譯這個表達式,因爲編譯器無法知道調用哪個構造函數。當然,在這種情況下,編譯器可以迫使這兩個參數來const B&,但由於某種原因,這是不是很相關的,它不會。

編輯:沒有,沒有,因爲沒有辦法編譯的表達,因爲它的重要數據(右值或左值)在運行時間變化。編譯器試圖通過轉換通過複製來構建右值來解決這個問題給你,但不能因爲它無法複製,因此它不能編譯。

+0

@DeadMG:從我的閱讀中,結果不會被複制。另一個表達呢。試圖弄清楚這是否重要。 – 2011-03-29 22:28:20

+0

劃傷我早先的陳述,我使用了錯誤的規則。 – 2011-03-29 22:34:55

+0

@ HighCommander4:它不起作用,因爲編譯器無法保證有關結果的事實。無論這個事實是否是這個類型,或者這個表達是右值還是左值,都是無關緊要的。 – Puppy 2011-03-29 22:42:02

7

[expr.cond](措詞從n3242草案):

否則,如果所述第二和第三操作數具有不同的類型和或者具有(可能CV修飾)類型,或同一的,如果兩者都是glvalues值類別和同型除了CV-資格,則嘗試對每個那些操作數轉換成其他的類型。用於確定操作數表達T1類型的E1是否可以轉換,以匹配T2類型的操作數表達E2過程定義德音響如下:

  • 如果E2是一個左值:E1可以轉換E2如果E1匹配可以隱式轉換(第4)的類型「左值參照T2」,受約束的是,在轉換的參考必須直接(8.5.3)綁定到左值。
  • 如果E2是一個x值:E1可以被轉換以匹配E2如果E1可以隱式轉換到輸入「右值參照T2」,受該基準必須直接結合的約束。
  • 如果E2是一個rvalue或者如果既不以上的轉化可以做到和操作數具有(可能CV修飾)類型中的至少一個:

    • 如果E1E2具有類類型,並且基礎類的類型相同或者一個是另一個的基類:如果T2的類與T1的類或基類T1以及cv的類相同,則E1可以轉換爲匹配E2T2的資格與cv資格0相同或更高的cv資格。如果應用了轉換,E1通過從E1複製初始化T2類型的臨時文件並將該臨時文件用作轉換後的操作數而更改爲類型T2的預值。

此規則提到複製初始化,但它並不適用,因爲兩個操作數具有相同的類型

如果第二個和第三個操作數的值相同類別的glvalues和具有相同類型,結果 屬於該類型和值類別,如果第二個或第三個操作數是位域,或者兩者均爲位域,則結果爲位域。

此規則不適用,因爲S()是一個右值,my_other_S是一個左值。

否則,結果是一個prvalue。如果第二個和第三個操作數不具有相同的類型,並且具有(可能爲cv-qualified)類類型,則使用重載決策來確定要應用於操作數的轉換(如果有的話)(13.3.1.2,13.6) 。如果重載解決失敗,則該程序不合格。否則,將應用由此確定的轉換,並使用轉換後的操作數代替本節其餘部分的原始操作數 。 在第二個和第三個操作數上執行左值到右值(4.1),數組到指針(4.2)和函數到指針(4.3)的標準轉換。在這些轉換之後,以下之一將成立:

  • 第二個和第三個操作數具有相同的類型;結果是那種類型。如果操作數具有類類型,則結果是臨時結果類型的臨時值,根據第一個操作數的值從第二個操作數或第三個操作數複製初始化

該規則適用,結果是複製初始化(強調我的)。

4

這是一個古老的已知問題。看到這裏

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#446

根據該委員會的決定,在你的榜樣的?:運營商應該總是返回一個臨時對象,這意味着對my_other_s分支,原my_other_s對象將被複制。這就是編譯器需要拷貝構造函數的原因。

該語言尚未在C++ 03中,但許多編譯器從一開始就實現了這種方法。

1

至於你更新的問題,如果S的定義的修改被允許 ,下面的工作,各地可能會有所幫助:

struct S 
{ 
    ... 
    S& ref() { return *this; } // additional member function 
    ... 
}; 

(some_condition ? S().ref() : my_other_S).f(); 

希望這有助於

+0

是的!這正是我對原始代碼的意圖。但不幸的是,我不得不編寫和調用一個特殊的成員函數來實現它... – HighCommander4 2011-03-30 20:30:01

相關問題