2016-10-11 36 views
0

我有一類服務器,該服務器有一個構造函數:爲什麼當我沒有傳遞類的實例時調用了複製構造函數?

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); 
           ^

現在,我明白爲什麼複製構造函數被隱式刪除,但爲什麼被調用?

如果向該類添加複製構造函數,錯誤消失。有沒有其他方法可以避免這種情況?

+0

我敢肯定,這是因爲它初始化右側的變量,然後複製到變量 – Matriac

+0

請注意,它不太可能被調用(你可以通過寫一個拷貝構造函數來打印某些東西來測試你的假設。可能構成[mcve]的一部分。) – juanchopanza

回答

4

Server server = Server(port);明確使用構造函數是copy initialization;您正在從臨時Server初始化server

copy elision可能會發生,但不能保證,直到C++ 17。即使拷貝/移動構造函數可能不會被調用,但仍然必須存在並且可訪問(就好像根本沒有發生任何優化),否則該程序是不合格的。

你可以將其更改爲direct initialization,這將直接調用Server::Server(int)

Server server(port); 

或者direct list initialization(由於C++ 11):

Server server{port}; 

編輯

由於C++ 17,copy elision爲這種情況保證。

Under the following circumstances, the compilers are required to omit the copy- and move- constructors of class objects even if copy/move constructor and the destructor have observable side-effects:

  • In initialization, if the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object:

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

因此,您的代碼將與C++ 17一起使用;爲保證副本的精確性,複製/移動構造函數不需要可訪問。

LIVE DEMO from GCC

2

因爲您複製初始化server對象。

定義

Server server = Server(port); 

相當於

Server server(Server(port)); 

你可能想通過做

Server server(port); 
+1

只是爲了迂腐,通常'T a = x;'相當於'T a(T(x));'。所以'服務器服務器=服務器(端口);'實際上等同於'服務器服務器(服務器(服務器(端口)))''。 :)你可以看到爲什麼copy-elison是一件事情。 – GManNickG

+3

@GManNickG:除非在文檔的最新版本中對其進行了修改,否則在LHS和RHS中對相同類型的初始化進行復制始終以特殊方式處理:'T a = x;'立即等同於'T a (x);',不需要涉及通用複製elision。複製構造函數仍然需要存在。 – AnT

+0

@AnT:啊,我不知道! – GManNickG

2

複製初始化,則=語法爲Server server = Server{port};,需要一個拷貝構造函數或轉移構造函數存在並且可訪問。

因爲你的拷貝構造函數不存在,試圖提供一個移動構造函數。

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

3

從非常令人討厭的迂腐的角度來看,目前提供的許多答案(如果不是全部)都有些誤導。

在左側和右側使用相同類型的C++複製初始化以特殊方式處理:它立即被解釋爲等價的直接初始化。

從[dcl.init]/16:

— If the destination type is a (possibly cv-qualified) class type:

— If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered...

這意味着,您的副本初始化

Server server = Server(port); 

直接初始化實際上是處理

Server server(Server(port)); 

,並按照直接初始化的規則進一步處理。

直接初始化規則說,重載決議是用來選擇一個構造函數和在這種情況下選擇的構造函數是複製構造函數(這是刪除你的情況,因此錯誤)。

因此,最終結果是相同的 - 複製構造函數是必需的。但是,它所要求的標準邏輯的「分支」不是負責複製初始化的,而是負責直接初始化的。

在這種情況下,區別純粹是概念性的。但是在C++ 98的這些日子裏,這個模糊的區別在[現在被遺忘的]指針的功能中起了重要的作用(re:auto_ptr_ref以及它的工作原理)。實際上,這經常被看作是移動構造模式的早期慣用實現(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),這很好。

相關問題