2017-08-08 130 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进行保证的返回值优化。 希望这有助于。