簡體   English   中英

當我不傳遞類的實例時,為什么調用復制構造函數?

[英]Why is the copy constructor called when I am not passing an instance of the class?

我有一個具有構造函數的Server類:

Server::Server(int port) {
    // initialize some class variables
    port_ = port;
    //...
}

我嘗試創建此類的實例,如下所示:

int main(int argc, char** argv) {
    int port = 3000;
    Server server = Server(port);
}

我得到這個編譯錯誤:

server_main.cpp:32:32: error: use of deleted function ‘Server::Server(const Server&)’
     Server server = Server(port);
                                ^

現在,我了解了為什么隱式刪除了副本構造函數,但是為什么要調用它呢?

如果將復制構造函數添加到類中,該錯誤就會消失。 還有其他方法可以避免這種情況嗎?

Server server = Server(port); 復制初始化 ; 您正在從臨時Server初始化server

復制省略可能會發生,但要等到C ++ 17才能保證。 甚至copy / move-constructor可能不會被調用,但仍然必須存在並且可以訪問(好像根本沒有優化發生),否則程序格式錯誤。

您可以將其更改為直接初始化 ,這將直接調用Server::Server(int)

Server server(port);

直接列表初始化 (自C ++ 11起):

Server server{port};

編輯

從C ++ 17開始,在這種情況下可以保證復制省略

在以下情況下,即使復制/移動構造函數和析構函數具有明顯的副作用,也要求編譯器忽略類對象的復制和移動構造函數:

  • 在初始化中,如果初始化器表達式是prvalue,並且源類型的cv不合格版本與目標類是同一類,則初始化器表達式用於初始化目標對象:

    T x = T(T(T())); // only one call to default constructor of T, to initialize x

因此,您的代碼將與C ++ 17配合使用; 為了保證復制省略,不需要訪問復制/移動構造函數。

來自GCC的現場演示

從非常令人討厭的書呆子角度來看,許多當前提供的答案(如果不是全部的話)會產生誤導。

在C ++中,左側和右側具有相同類型的復制初始化以特殊方式處理:它立即被解釋為等效的直接初始化。

從[dcl.init] / 16:

—如果目標類型是(可能是cv限定的)類類型:

—如果初始化是直接初始化, 或者是復制初始化,其中源類型的cv不合格版本與destination的類相同,或者是其派生類 ,則考慮構造函數...

這意味着您的副本初始化

Server server = Server(port);

實際上被當作直接初始化

Server server(Server(port));

並根據直接初始化的規則進行進一步處理。

直接初始化的規則說,重載解析用於選擇一個構造函數,在這種情況下選擇的構造函數是copy-constructor(在您的情況下被刪除,因此是錯誤)。

因此,最終結果是相同的-需要復制構造函數。 但是,要求它的標准邏輯的“分支”不是負責復制初始化的,而是負責直接初始化的。

在這種情況下,差異純粹是概念上的。 但是,在C ++ 98時代,這種模糊的區別在[現在已被遺忘的] std::auto_ptr指針(re: auto_ptr_ref及其工作方式)的功能中發揮了重要作用。 實際上,這通常被視為Move Constructor模式的早期慣用實現( https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Move_Constructor )。


一個簡單的示例說明特殊處理可能如下所示

struct A
{
    A() {}
    A(A&) {}
    A(int) {}
    operator int() const { return 42; }
};

struct B
{
    B();
    operator int() const { return 42; }
};

int main()
{
    A a1 = A(); // OK
    A a2 = B(); // Error
}

請注意,即使右側的兩個類都提供了用戶定義的int轉換,也只有第一個初始化才編譯並使用A::A(int)構造函數。 第二個失敗。

第二次初始化按照通常的復制初始化規則進行。 為了成功,它需要兩次用戶定義的轉換( B -> intint -> A ),這些轉換不能隱式完成。

根據直接初始化規則處理第一次初始化,從而有效地使int -> A轉換成為顯式。 現在,此初始化僅需要一個隱式用戶定義的轉換( A -> int ),這很好。

因為您復制初始化server對象。

定義

Server server = Server(port);

相當於

Server server(Server(port));

您可能想通過以下方式顯式使用構造函數

Server server(port);

復制初始化=語法如Server server = Server{port}; ,要求復制構造函數或move構造函數存在且可訪問。

由於您的副本構造函數不存在,請嘗試提供move構造函數。

如果不能,那么唯一的辦法就是使用直接初始化語法,例如Server server{port};

暫無
暫無

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

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