c++ - std::move 意外调用析构函数

标签 c++ c++11 move-semantics

我一直在尝试编写一个不能复制但可以 move 的类,并且只能使用命名构造函数创建。我使用下面的 namedConstructor3 实现了我的目标。但是,我不明白为什么 namedConstructor2 失败了。

struct  A
{
    int a;

    //static A && namedConstructor1( int a_A )
    //{
    //  A d_A( a_A );
    //  return d_A;     // cannot convert from A to A&&
    //}

    static A && namedConstructor2( int a_A )
    {
        wcout << L"Named constructor 2\n";
        A d_A( a_A );
        return move( d_A );
    }

    static A namedConstructor3( int a_A )
    {
        wcout << L"Named constructor 3\n";
        A d_A( a_A );
        return move( d_A );
    }

    A( A && a_RHS ) : a( a_RHS.a )
    {
        a_RHS.a = 0;
        wcout << L"\tMoved: a = " << a << endl;
    }

    ~A()
    {
        wcout << L"\tObliterated: a = " << a << endl;
        a = -a;
    }

    A( const A & ) = delete;
    A & operator =( const A & ) = delete;

protected:
    A( int a_A = 0 ) : a( a_A )
    {
        wcout << L"\tCreated: a = " << a << endl;
    }
};

int main()
{
    A d_A2 = A::namedConstructor2( 2 );
    A d_A3 = A::namedConstructor3( 3 );
    wcout << "Going out of scope\n";
    return 0;
}

输出是

Named constructor 2
        Created: a = 2
        Obliterated: a = 2
        Moved: a = -2
Named constructor 3
        Created: a = 3
        Moved: a = 3
        Obliterated: a = 0
Going out of scope
        Obliterated: a = 3
        Obliterated: a = -2

问题:

  1. 为什么析构函数在 namedConstructor2 中的 move 构造函数之前被调用,正如输出的第 3 行和第 4 行所证明的那样?

  2. 为什么被 move 的构造函数仍然可以使用被销毁的数据(输出的第 4 行证明了这一点)?

  3. std::move 的签名引导我的意义上,namedConstructor2 是否比 namedConstructor3“更自然”认为 std::move 的返回值有“两个&&”?

template< class T >
typename std::remove_reference<T>::type&& move( T&& t )

使用 VS2013u4 编译。


编辑

Deduplicator 的回答让我很满意。此编辑是为了完整起见。答案和评论表明 namedConstructor3 是“次优”。我加了

static A namedConstructor4( int a_A )
{
    wcout << L"Named constructor 4\n";
    A d_A( a_A );
    return d_A;
}

到类和 A d_A4 = A::namedConstructor4( 4 );main 函数。新输出(在 Release模式下编译时,而不是在 Debug模式下)显示最佳情况甚至不 move 对象:

Named constructor 2
        Created: a = 2
        Obliterated: a = 2
        Moved: a = -2
Named constructor 3
        Created: a = 3
        Moved: a = 3
        Obliterated: a = 0
Named constructor 4
        Created: a = 4
Going out of scope
        Obliterated: a = 4
        Obliterated: a = 3
        Obliterated: a = -2

最佳答案

std::move(lvalue or xvalue) 不调用析构函数。

它只是将传递的引用更改为右值引用,因此 move 语义适用。

那么,为什么你的本地销毁得太早了?
很简单,返回一个局部变量的引用就是UB:
Can a local variable's memory be accessed outside its scope?

逐一检查您的命名构造函数:

  1. namedConstructor1 返回右值引用。
    但是您尝试返回一个本地值(它是一个左值),编译器会提示该错误。
  2. namedConstructor2 原则上与 namedConstructor1 相同,但您添加了一个显式转换(以 std::move 的形式)左值到右值引用,这会关闭编译器。
    因此,您会因为对编译器说谎而得到 UB,特别是局部生命周期在函数结束时结束,在返回的右值引用被使用之前。
  3. namedConstructor3 没问题,但不是最理想的。
    您正在使用右值引用来初始化返回值(不是引用)。
    次优部分是由于 std::move 禁用返回值优化,这将避免在这种情况下实现实际 move (或复制)的需要。

关于c++ - std::move 意外调用析构函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27284651/

相关文章:

c++ - 在 Linux 上与静态 C 运行时链接的共享库和可执行文件。他们每个人都像Windows一样有单独的堆吗?

c# - 如何在 64 位项目中引用 32 位 DLL?

c++ - 我是否正确使用 move 语义?会有什么好处?

c++ - 在 C++ 中,为什么在嵌套的 unordered_map 上调用函数需要 move 构造函数?

c++ - 析构函数隐藏在这段代码的什么地方?

c++ - 当 C 和 C++ 中严格要求内存释放时?

c++ - wait_until 在主线程中的工作方式与主线程中的工作方式是否不同?由 小码哥发布于

c++ - 为什么标准区分直接列表初始化和复制列表初始化?

c++ - 为什么必须在父类(super class)中实现虚函数?

c++ - 将结构中的 Vector 分配给 main 中 A Vector 的内容