1

最近一直在研究 C++11 中的移动语义。我印象深刻,迫不及待地想弄脏手并尝试一下。以下是我的代码:

#include <iostream>
using namespace std;

class ArrayWrapper
{
public:
    // default constructor produces a moderately sized array
    ArrayWrapper ()
        : _p_vals( new int[ 64 ] )
        , _size( 64 )
    {
        cout << "Default constructor: " << this << endl;
    }

    explicit ArrayWrapper (int n)
        : _p_vals( new int[ n ] )
        , _size( n )
    {
        cout << "Constructor: " << this << endl;
    }


    // move constructor
    ArrayWrapper (ArrayWrapper&& other)
        : _p_vals( other._p_vals  )
        , _size( other._size )
    {
            cout << "Move constructor: " << this << endl;
            other._p_vals = NULL;
            other._size = 0;
    }


    // copy constructor
    ArrayWrapper (const ArrayWrapper& other)
        : _p_vals( new int[ other._size  ] )
        , _size( other._size )
    {
            cout << "Copy constructor: " << this << endl;
            for ( int i = 0; i < _size; ++i )
            {
                    _p_vals[ i ] = other._p_vals[ i ];
            }
    }
    ~ArrayWrapper ()
    {
            cout << "Destructor: " << this << endl;
            delete [] _p_vals;
    }

    void self () {
        cout << "This address: " << this << endl;
    }

public:
    int *_p_vals;
    int _size;
};

ArrayWrapper two() {
    ArrayWrapper a(7);
    cout << "Temp ArrayWrapper created!" << endl;
    return a;
}

int main() {
    ArrayWrapper b (two());
    b.self();
}

(我从1引用了一些代码)

代码可能看起来很长,但实际上非常幼稚,只是一个转储数组。

在第 67 行,我特意用右值创建了 b,并希望看到移动构造函数是如何被调用的。但令人失望的是,这个程序的输出是:

Constructor: 0x7fff51d60be0
Temp ArrayWrapper created!
This address: 0x7fff51d60be0
Destructor: 0x7fff51d60be0

打印出来的三个地址是一样的,根本没有调用move构造函数!事实上,我后来尝试删除移动构造函数,程序仍然编译并给出了相同的输出!而且如果你仔细看,你会发现构造函数只被调用一次,在构造a的时候。也就是说,在构造 b 时,根本不会调用构造函数,既不移动也不复制!

我真的很困惑。谁能告诉我为什么没有触发移动构造函数,以及究竟 b 是如何构造的?

4

2 回答 2

5

您所遇到的情况称为复制省略——在某些情况下可以跳过复制和移动构造函数,即使跳过它们会产生明显的副作用。常见情况包括 RVO 和 NRVO(返回值优化和命名 RVO)或来自匿名临时的复制初始化。

要阻止它,请std::move在从函数返回的位置以及在 中构造值的位置显式调用main,因为std::move的右值强制转换使省略是非法的。 std::move接受 a T&orT&&并将其转换为 aT&&以防止“左侧”执行省略,因为省略(至少在这一点上)仅限于某些狭窄的情况。

阻止省略通常是一个坏主意,但std::move会这样做。

于 2014-01-30T05:30:26.700 回答
5

这是因为 RVO(返回值优化)技巧。对象 fromArrayWrapper two();代替ArrayWrapper b;. 这就是为什么只有一种构造+破坏。

尝试将其修改为:

ArrayWrapper two(bool disable_rvo) {
    ArrayWrapper a(7);
    cout << "Temp ArrayWrapper created!" << endl;
    if (disable_rvo) 
        return a;
    else
        return ArrayWrapper(8);
}

int main() {
    ArrayWrapper b (two(true));
    b.self();
}

并关闭优化。

于 2014-01-30T13:10:40.207 回答