c大括号内的{* this}
下面的代码编译得很好:
g++ -std=c++11 test.cpp -Wall -Wextra -Wfatal-errors && ./a.out
但是,如果我从{*this}
删除大括号,并使用*this
替代,我将面对错误:
错误:使用已删除的函数'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; }
两者之间的差异真的很微妙。 C ++ 11引入了特征列表初始化 (有时也被称为括号初始化 ):
在C ++ 11之前,当你想默认构造Obj
types的对象o
并从o
构造一个Position p
时,你必须写
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
第一行声明了一个函数 ,第二行试图使用一个构造函数来创build一个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
对象,然后从这个临时对象间接初始化返回值,由于copy和move构造函数都被明确地删除。
正如前面的海报所指出的那样,大多数编译器将这些临时对象删除,因为它们对于任何事情都没有什么用处。 但是这只有在理论上可以使用时才有可能,因为复制或移动构造函数都是可用的。 因为这会导致很多混淆(为什么我的return语句需要大括号?是编译器会不会去复制?),C ++ 17会消除这些不必要的临时对象,并直接初始化返回值两种情况( return {*this};
并return *this
)。
您可以使用支持C ++ 17的编译器来尝试此操作。 在clang 4.0或gcc 7.1中,你可以传递--std=c++1z
,你的代码应该可以很好地编译。
当大括号存在时,您是复制列表 – 初始化返回值,不涉及复制/移动构造函数。 返回值是使用Position(const Obj&)
构造Position(const Obj&)
就地构造的。
请注意,如果您将Position(const Obj&)
构造函数explicit
则即使使用大括号,代码也将无法编译,因为copy-list-initialization不允许显式构造函数被调用。
如果省略花括号,则语义上在该函数内构造一个临时的Position
对象,并且返回值是从该临时对象构造的。 在实践中,大部分的实现都会使移动的构造变得迟钝,但是它仍然需要一个可行的移动构造函数来存在,在这里不是这样,因为它已经被明确地删除了。 这就是你的代码不能在没有大括号的情况下编译的原因。
使用C ++ 17编译器,由于保证了copy-elision,所以即使没有大括号也能编译代码。
这个不错! 这是因为return {...}
意思是“返回函数的返回types的对象初始化与列表初始化…”。
列表初始化器在这里有更详细的描述:
http://en.cppreference.com/w/cpp/language/list%20initialization
所以,区别在于{*this}
称之为:
inline Position(const Obj& ref): ref(ref){}
尽pipe*this
尝试通过使用显式删除的赋值运算符(前C + + 11,转换成Obj&
Position
,他们将不得不被private
,如果列表初始化将可用,你会得到一个更令人混淆的错误消息…) :
inline Position(Position const &) = delete; inline Position(Position &&) = delete;
坦率地说,使用你的类和下面的main():
int main() { Obj o1; cout<<"get position"<<'\n'; Obj::Position pos= o1.get_pos(); cout.flush(); return 0; }
它在两种情况下(-std = c ++ 14)都没有编译(gcc / mingw),有或没有大括号,它抱怨丢失的Position(Position &&)构造函数被删除。 这是合理的,因为似乎在这两种情况下都执行临时返回对象的构造,然后将其移动到目的地。 移动构造函数被删除是不可能的。 相反,使用-std = c ++ 17标记它在两种情况下编译(有或没有花括号),最有可能的是,我们正在对c ++ 17进行保证的返回值优化。 希望这可以帮助。