C++中大括号内的{*this}

51

以下代码编译正常:

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;
}
4个回答

38

当出现花括号时,您正在使用 复制列表初始化 来初始化返回值,不涉及任何复制/移动构造函数。使用 Position(const Obj&) 构造函数在原地构造返回值

请注意,即使您使用了花括号,如果将Position(const Obj&)构造函数声明为explicit,代码仍将无法编译,因为复制列表初始化不允许调用显式构造函数。

如果省略花括号,则在函数内部语义上构造了一个临时的Position对象,并从该临时对象进行移动构造以构造返回值。实际上,大多数实现都会省略移动构造,但仍需要存在可行的移动构造函数,而在这里已被明确删除。这就是没有花括号代码无法编译的原因。

使用C++17编译器时,由于保证复制省略,即使没有花括号,您的代码也将编译。


当我省略大括号时,为什么错误信息中显示的是 Position(Obj::Position&&) 而不是 Position(Obj::Position&) 呢?看起来相反了。 - ar2015
@ar2015 你是指为什么错误信息里看到的是Position(Position&&)而不是Position(Position const&)吗?这是因为你有一个prvalue Position对象来初始化返回值,移动构造函数就更匹配。如果你注释掉inline Position(Position &&) = delete;这一行,错误信息将包含复制构造函数。 - Praetorian
基本上是一种“禁止了它,然后我试图去做”的情况。如果确实需要这样做。 - Swift - Friday Pie

19

这两者之间的区别非常微妙。C++11引入了列表初始化特性(有时也称为花括号初始化):

在C++11之前,当您想要默认构造类型为Obj的对象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 

第一行声明了一个函数,第二行尝试使用接受函数指针作为参数的构造函数创建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,您的代码应该可以在有或没有括号的情况下编译良好。


14
这是一个好的例子!这是因为return {...}的意思是“返回一个用列表初始化程序初始化的函数返回类型的对象”。
更多关于列表初始化程序的详细信息在这里描述: http://en.cppreference.com/w/cpp/language/list%20initialization 因此,不同之处在于{*this} 调用这个:
inline Position(const Obj& ref): ref(ref){}

与此同时,*this 试图通过使用明确删除的赋值运算符(在C++11之前,它们必须被设置为private,如果列表初始化程序可用,则会获得更令人困惑的错误消息...)将 Obj& 转换为 Position

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;
}

在两种情况下(-std=c++14),无论是否使用花括号,它都无法编译(gcc/mingw),并且抱怨缺少Position(Position&&)构造函数,该构造函数已被删除。这是合理的,因为似乎在两种情况下都执行了临时返回对象的构造,然后将其移动到目标位置。由于移动构造函数已被删除,这是不可能的。 相反,使用-std=c++17标志,在两种情况下(有或没有花括号)都可以编译,因为我们很可能正在命中c++17的保证返回值优化。 希望这可以帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接