以下代码编译良好:
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;
}
答案 0 :(得分:37)
当花括号出现时,你重新copy-list-initializing返回值,不涉及复制/移动构造函数。使用Position(const Obj&)
构造函数的return value is constructed in-place。
请注意,如果您创建Position(const Obj&)
构造函数explicit
,即使使用大括号也无法编译代码,因为 copy-list-initialization 不允许使用显式构造函数被称为。
如果省略花括号,则在语义上在函数内构造一个临时Position
对象,并从该临时值构造返回值。实际上,大多数实现都会忽略移动构造,但它仍然需要一个可行的移动构造函数才能存在,而这种情况并非如此,因为它已被明确删除。这就是你的代码在没有大括号的情况下无法编译的原因。
使用C ++ 17编译器,由于guaranteed copy-elision,您的代码即使没有大括号也会编译。
答案 1 :(得分:16)
两者之间的区别非常微妙。 C ++ 11引入了功能列表初始化(有时也称为大括号初始化):
在C ++ 11之前,当你想要o
类型的默认构造和对象Obj
并从Position p
构造o
时,你必须写< / 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
第一行声明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
- 语句,这导致了您的问题的答案:return {*this};
和return *this;
之间的区别在于前者初始化返回值<来自*this
的强>直接,而后者首先将*this
转换为临时Position
对象,然后从此临时间接初始化返回值,因为复制和移动构造函数都已被明确删除而失败。
正如之前的海报所指出的那样,大多数编制者都忽略了这些临时物品,因为它们对任何事物都没有用处;但这只有在理论上可以使用时才有可能,因为副本或移动构造函数都可用。因为这会导致很多混乱(为什么我需要在返回语句周围使用括号?编译器是否会删除副本?),C ++ 17会消除这些不必要的临时值,并初始化返回值直接在两种情况下(return {*this};
和return *this
)。
您可以使用支持C ++ 17的编译器来尝试此操作。在clang 4.0或gcc 7.1中,您可以传递--std=c++1z
,并且您的代码应该可以使用和不使用大括号进行编译。
答案 2 :(得分: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;
答案 3 :(得分:-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),有或没有花括号,它抱怨缺少位置(位置和&amp;&amp;)构造函数,它被删除。这是合理的,因为似乎在两种情况下都执行临时返回对象的构造,然后将其移动到目的地。这是不可能的,因为删除了移动构造函数。 相反,使用-std = c ++ 17标志它会在两种情况下编译(有或没有花括号),因为,最有可能的是,我们正在进行c ++ 17的保证返回值优化。 希望这会有所帮助。