【问题标题】:std::move calls the destructor unexpectedlystd::move 意外调用析构函数
【发布时间】:2014-12-04 01:16:55
【问题描述】:

我一直在尝试编写一个无法复制但可以移动的类,并且除了使用命名构造函数之外无法创建。我通过下面的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 中的移动构造函数之前调用析构函数,如输出的第 3 行和第 4 行所示?

  2. 为什么被移动的构造函数仍然可以使用销毁的数据(如输出的第 4 行所示)?

  3. namedConstructor2不是比namedConstructor3“更自然”吗?std::move的签名让我认为std::move的返回值有“两个&&”?

​​​​​

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

用 VS2013u4 编译。


编辑

Deduplicator 的回答让我很满意。此编辑是为了完整起见。答案和 cmets 建议 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 函数。新的输出(在发布模式下编译时,而不是在调试模式下)显示最佳情况甚至不移动对象:

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

【问题讨论】:

  • 您的程序具有未定义的行为,因为构造函数“2”返回对非静态局部变量的引用。
  • @KerrekSB:但是构造函数 3 具有相同类型的变量,不是吗?毕竟,他们的身体是一样的。
  • 他们的返回类型不是,这一切都不同。因此namedConstructor1 出错了,而在namedConstructor2 中你对编译器撒谎并获得了UB,而在namedConstructor3 中你只是对move 感到悲观。
  • @Hector 不完全是,返回值不是“本地临时”(至少不是您的函数本地,而是调用者本地)
  • @Hector:返回值不是变量。只是一个值,具体来说就是函数调用表达式的值。

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


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 嗯,是返回自身UB的行为,还是尝试使用返回的引用UB?
  • @T.C.:不太确定。我认为如果你忽略返回值,你可能没问题。
猜你喜欢
  • 1970-01-01
  • 2020-04-14
  • 2014-03-13
  • 1970-01-01
  • 2014-01-02
  • 1970-01-01
  • 2013-12-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多