2017-08-08 131 views
49

內部下面的代碼編譯細:C++ {*此}花括號

g++ -std=c++11 test.cpp -Wall -Wextra -Wfatal-errors && ./a.out 

但是,如果我從{*this}除去大括號和使用*this相反,我將與錯誤面臨:

error: use of deleted function ‘Obj::Position::Position(Obj::Position&&)’

{*this}*this有什麼區別?

class Obj 
{ 
    template<bool> friend class Position; 

    double data; 
public: 
    class Position 
    { 
     const Obj& ref; 
    public: 
     inline Position(const Obj& ref): ref(ref){} 
     inline Position(Position const &) = delete; 
     inline Position(Position &&) = delete; 
    }; 
    inline Obj(){} 
    inline Obj(const double &data): data(data){} 
    inline auto get_pos() const-> Position{return {*this};} /* <--- here */ 
    inline auto get_pos()-> Position{return {*this};} 
}; 

int main() 
{ 
    return 0; 
} 

回答

13

兩者之間的差異真的很微妙。 C++ 11引入了功能列表初始化(有時也稱爲撐初始化):

C++ 11之前,當你想通過缺省方式構造和對象ObjoPosition po構建,你必須寫

Obj o;    // default construct o 
Obj::Position p(o); // construct p using Position(Obj const&) 

初學者(尤其是與Java背景)一個常見的錯誤是試圖寫:

Obj o();   // mistake: declares a function o returning an Obj 
Obj::Position p(o); // error: no constructor takes a function 

第一行聲明一個function,第二行嘗試使用一個構造函數創建一個Position,該構造函數將函數指針作爲其參數。爲了有一個統一的初始化語法,C++ 11引入了列表初始化:

Obj oo{};    // new in C++11: default construct o of type Obj 
Obj::Position p1(oo); // possible before (and after) C++11 
Obj::Position p2{oo}; // new in C++11: construct p2 using Position(Obj const&) 

這種新的語法也適用於return -statements,這導致您的問題的答案:return {*this};return *this;之間的區別是,前者直接從初始化*this返回值,而後者則第一轉換*this到臨時Position對象,然後間接從該臨時初始化返回值,這將失敗,因爲這兩個禁止複製和移動,構造有已被明確刪除。如前面的海報所指出的,大多數編譯器都會將這些臨時對象刪除,因爲它們對任何事情都沒有任何用處;但只有在理論上可以使用它們,因爲複製或移動構造函數都可用。因爲這會導致很多混淆(爲什麼我的返回語句需要大括號?編譯器是否會刪除複製或不?),C++ 17將取消這些不必要的臨時對象,並直接初始化返回值return {*this};return *this)。

您可以使用支持C++ 17的編譯器來嘗試此操作。在clang 4.0或gcc 7.1中,您可以通過--std=c++1z,而且您的代碼應該可以在帶括號和不帶括號的情況下正常編譯。

38

當大括號都存在,你copy-list-initializing的返回值,沒有複製/移動構造參與。 return value is constructed in-place使用Position(const Obj&)構造函數。

請注意,如果你做的Position(const Obj&)構造explicit因爲副本列表初始化不允許被稱爲顯式構造方法的代碼會失敗,即使在大括號進行編譯。

如果您省略了大括號,則語義上會在該函數內構造一個臨時Position對象,並且返回值將從該臨時對象構造而來。在實踐中,大多數實現將會避免移動構造,但它仍然需要一個可行的移動構造函數來存在,但這裏並不是這樣,因爲它已被明確刪除。這是你的代碼不能在沒有大括號的情況下編譯的原因。

使用C++編譯器17,你的代碼將編譯即使沒有因爲guaranteed copy-elision花括號。

+0

我不知道爲什麼當我省略括號時,我在錯誤中看到'Position(Obj :: Position &&)''而不是'Position(Obj :: Position&)''。它看起來相反。 – ar2015

+0

@ ar2015你是說你爲什麼在錯誤信息中看到'Position(Position &&)'而不是'Position(Position const&)'?這是因爲你有一個prvalue'Position'對象來初始化返回值,移動構造函數是一個更好的匹配。如果註釋掉'inline Position(Position &&)= delete;'行,則錯誤消息將包含複製構造函數。 – Praetorian

+0

它基本上是一個「我禁止它,然後我試圖這樣做」的情況「如果這實際上是必需的 – Swift

13

這是一個很好的!這是因爲return {...}的意思是「返回函數的返回類型的對象,使用列表初始化器...初始化」。

列表初始化中更詳細地描述如下:

http://en.cppreference.com/w/cpp/language/list%20initialization

所以,不同的是,{*this}稱此爲:

inline Position(const Obj& ref): ref(ref){} 

*this嘗試通過使用轉換Obj&Position顯式刪除賦值操作符(在C++ 11之前,它們必須被製作爲private,而且你會更加困惑如果錯誤信息列表初始化將可...):

inline Position(Position const &) = delete; 
inline Position(Position &&) = delete; 
-2

坦率地說,使用類和以下的main():

int main() 
{ 
    Obj o1; 
    cout<<"get position"<<'\n'; 
    Obj::Position pos= o1.get_pos(); 


    cout.flush(); 
    return 0; 
} 

它不會在這兩種編譯器(gcc/MinGW的)例(-std = C++ 14),有或沒有大括號和它報告丟失位置(位置& &)構造,其中將被刪除。這是合理的,因爲看起來在這兩種情況下都執行臨時返回對象的構造,然後將其移至目的地。移動構造函數被刪除是不可能的。 相反,使用-std = C++ 17標誌它會在兩種情況下編譯(帶或不帶大括號),因爲很可能我們正在對C++ 17進行保證的返回值優化。 希望這有助於。