簡體   English   中英

為什么這里需要復制構造函數?

[英]Why is a copy constructor required here?

請考慮以下代碼:

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

我不明白為什么應該在該行上進行復制構造 - 目的是簡單地在默認構造的S實例或my_other_S上調用f() ,即它應該等效於:

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

第一種情況有什么不同,為什么需要復制構造函數?

編輯 :那么,有沒有辦法在表達式上下文中表達“在預先存在的對象上執行此操作”?

如果其中一個參數是rvalue,則?:的結果是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& ,但由於某些不太相關的原因,它不會。

編輯:不,沒有,因為沒有辦法編譯該表達式,因為關於它的重要數據(rvalue或lvalue)在運行時會有所不同。 編譯器嘗試通過復制構造轉換為rvalue來為您解決此問題,但它不能因為它無法復制,因此無法編譯。

[expr.cond] (草案n3242的措辭):

否則,如果第二個和第三個操作數具有不同的類型並且具有(可能是cv限定的)類類型,或者兩者都是相同值類別的glvalues和除cv-qualification之外的相同類型,則嘗試轉換每個那些操作數與另一種操作數的關系。 確定類型T1的操作數表達式E1是否可以轉換為匹配類型T2的操作數表達式E2的過程定義如下:

  • 如果E2是左值:如果E1可以被隱式轉換(第4條)到類型“左值引用T2 ”,則E1可以被轉換為匹配E2 ,受制於轉換中引用必須直接綁定的約束(8.5.3) )到左值。
  • 如果E2是一個x值: E1可以被轉換以匹配E2如果E1可以隱式轉換到輸入“右值參照T2 ”,受該基准必須直接結合的約束。
  • 如果E2是一個rvalue,或者上面的轉換都不能完成,並且至少有一個操作數具有(可能是cv-qualified)類類型:

    • 如果E1E2具有類類型,和底層類類型是相同的或一個是基類的其他的: E1可以被轉換以匹配E2如果類的T2是相同的類型,或基類的,類的T1 ,和的CV-資格T2是相同的CV-資格,或更大的CV-資格比,的CV-資格T1 如果應用轉換,則通過從E1復制初始化T2類型的臨時值並將該臨時值用作轉換后的操作數,將E1更改為類型T2的prvalue。

此規則提到了復制初始化,但由於兩個操作數具有相同的類型,因此不適用

如果第二個和第三個操作數是相同值類別的glvalues並且具有相同的類型,則結果是該類型和值類別,如果第二個或第三個操作數是位字段,則它是位字段,或者如果兩者都是位字段。

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

否則,結果是prvalue。 如果第二個和第三個操作數不具有相同的類型,並且具有(可能是cv限定的)類類型,則使用重載決策來確定要應用於操作數的轉換(如果有)(13.3.1.2,13.6) 。 如果重載決策失敗,則程序格式錯誤。 否則,應用如此確定的轉換,並使用轉換的操作數代替本節其余部分的原始操作數。 Lvalue-to-rvalue(4.1),array-to-pointer(4.2)和函數到指針(4.3)標准轉換在第二個和第三個操作數上執行。 完成轉換后,以下其中一項應成立:

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

應用此規則,結果是復制初始化(強調我的)。

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

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

根據委員會的決定,在您的示例中, ?:運算符應始終返回臨時對象,這意味着對於my_other_s分支,必須復制原始的my_other_s對象。 這就是編譯器需要復制構造函數的原因。

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

至於您更新的問題,如果允許修改S的定義,以下解決方法可能會有所幫助:

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

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

希望這可以幫助

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM