C++11 C++14 C++17 move semantics

最近刚刚学习了一下从C++11开始支持的move semantics,C++还是很神奇的。本文不涉及perfect forwarding。
下面代码测试了如下想法

  1. swap 两个plain array。
  2. swap 两个array of objects。
  3. 从函数返回std::vector
  4. std::vector进行直接赋值。
  5. 从函数返回对象。
  6. 对对象赋值。

本机系统 gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0。
编译debug版本,没有显式开启编译优化。

通过测试得到如下现象:

  1. 通过单步调试,发现在swap两个plain array时,实际上执行了循环对每个元素进行copy/move。在swap两个object array时,也是执行循环,对每个元素进行swap,期间采用了move操作。而两个array本身的地址没有发生变化,对象的地址也没有发生变化,对象的成员变量(int)的值互换。
  2. 无法对compile time长度不相等的array进行swap,无法编译。
  3. 形如std::vector<Value> values = create_values();这样创建和初始化一体的格式,经由函数create_values()返回的std::vector将调用 1次 Value的move constructor。并且create_values()的局部变量被完全move到其外部,valuesLocalvalues地址完全一致。这里为何只有1次Value的move操作非常迷。
  4. 形如values = create_values();这样创建、初始化和赋值分离的格式,将产生 1次 Value的 move constructor 的调用。create_values()的局部变量valuesLocal的内容被move到了函数外部,函数内外valuesLocalvalues的地址不同。
  5. 对於单个对象,形如v0 = std::move(v1);v0 = create_value(2);这样的赋值,将调用1次 move assignement operator。虽然是move assignment,但是Value的实现仍是将成员变量复制了1次,因为成员变量是一个primitive对象。
  6. 形如Value v2 = create_value(3); 这样的创建加初始化,没有调用任何move操作, 也没有调用copy assignment operator。temp与v2的地址完全相同。我认为这里被编译器进行了返回值优化,虽然我没有开启任何优化。

目前看直接返回std::vector产生的overhead很小,不会对内部元素进行复制。

代码如下

#include <iostream>
#include <string>
#include <vector>

#define SHOW_ARRAY(array, n) \
    show_array(array, n, #array);

#define SHOW_SEQ(seq) \
    show_seq(seq, #seq);

template < typename T >
static void show_array( const T *array, int n, const std::string &name ) {
    std::cout << name << " " << array << ": ";
    for ( int i = 0; i < n; ++i ) {
        std::cout << &array[i] << " " << array[i] << ", ";
    }
    std::cout << "\n";
}

template < typename T >
static void show_seq( const T &seq, const std::string &name ) {
    std::cout << name << ": \n";
    for( const auto& v : seq ) {
        std::cout << v << ", ";
    }
    std::cout << "\n";
}

class Value {
public:
    Value() = default;

    Value(int v)
        : val{v}
    {}

    Value( const Value &other ) {
        val = other.val;
    }

    Value ( Value &&other ) noexcept {
        if ( flag ) {
            std::cout << "Move constructor. " << std::endl; // Force flush output.
        }
        val = other.val;
    }

    ~Value() = default;

    Value& operator= ( const Value &other ) {
        if ( flag ) {
            std::cout << "Copy assignment. " << std::endl; // Force flush output.
        }
        this->val = other.val;
        return *this;
    }

    Value& operator= ( Value &&other ) noexcept {
        if ( flag ) {
            std::cout << "Move assignment. " << std::endl; // Force flush output.
        }
        this->val = other.val;
        return *this;
    }

    friend std::ostream& operator<< ( std::ostream &out, const Value &value ) {
        out << "val = " << value.val;
        return out;
    }

public:
    int val;

public:
    static bool flag;
};

bool Value::flag = false;

static Value create_value(int v) {
    Value temp(v);
    std::cout << "&temp = " << &temp << "\n";
    return temp;
}

static std::vector<Value> create_values() {
    std::vector<Value> valuesLocal;
    valuesLocal.emplace_back( 0 );
    valuesLocal.emplace_back( 1 );

    std::cout << "&valuesLocal = " << &valuesLocal << "\n";
    std::cout << "valuesLocal.data() = " << valuesLocal.data() << "\n";
    std::cout << "&valuesLocal[0] = " << &valuesLocal[0] << "\n";
    std::cout << "&valuesLocal[1] = " << &valuesLocal[1] << "\n";

    return valuesLocal;
}

int main( int argc, char **argv ) {
    std::cout << "Hello, TryMoveSemantics! \n";

    {
        std::cout << "========== Swap 2 plain arrays. ==========\n";
        int array0[2] { 0, 1 };
        int array1[2] { 2, 3 };
        SHOW_ARRAY(array0, 2)
        SHOW_ARRAY(array1, 2)

        std::swap( array0, array1 );

        SHOW_ARRAY(array0, 2)
        SHOW_ARRAY(array1, 2)
        std::cout << "\n";
    }

    {
        std::cout << "========== Swap 2 plain arrays of objects. ==========\n";
        Value array0[2] { 0, 1 };
        Value array1[2] { 2, 3 };

        SHOW_ARRAY(array0, 2)
        SHOW_ARRAY(array1, 2)

        std::swap( array0, array1 );

        SHOW_ARRAY(array0, 2)
        SHOW_ARRAY(array1, 2)
        std::cout << "\n";
    }

    {
        std::cout << "========== Return std::vector by move. ==========\n";
        Value::flag = true;
        std::cout << "Creation and initialization. \n";
        std::vector<Value> values = create_values();
        std::cout << "&values = " << &values << "\n";
        std::cout << "values.data() = " << values.data() << "\n";
        std::cout << "&values[0] = " << &values[0] << "\n";
        std::cout << "&values[1] = " << &values[1] << "\n";
        SHOW_SEQ(values)
        Value::flag = false;
        std::cout << "\n";
    }

    {
        std::cout << "========== Separate creation and assignment. ==========\n";
        Value::flag = true;
        std::cout << "Separated creation and initialization. \n";
        std::vector<Value> values {2, 3};
        SHOW_SEQ(values)
        values = create_values();
        std::cout << "&values = " << &values << "\n";
        std::cout << "values.data() = " << values.data() << "\n";
        std::cout << "&values[0] = " << &values[0] << "\n";
        std::cout << "&values[1] = " << &values[1] << "\n";
        SHOW_SEQ(values)

        std::vector<Value> valuesLong {2, 3, 4};
        std::cout << "&valuesLong = " << &valuesLong << "\n";
        std::cout << "valuesLong.data() = " << valuesLong.data() << "\n";
        SHOW_SEQ(valuesLong)
        valuesLong = create_values(); // Assign size 2 to size 3.
        std::cout << "&valuesLong = " << &valuesLong << "\n";
        std::cout << "valuesLong.data() = " << valuesLong.data() << "\n";
        std::cout << "&valuesLong[0] = " << &valuesLong[0] << "\n";
        std::cout << "&valuesLong[1] = " << &valuesLong[1] << "\n";
        std::cout << "valuesLong.size() = " << valuesLong.size() << "\n";
        SHOW_SEQ(values)
        Value::flag = false;
        std::cout << "\n";
    }

    {
        std::cout << "========== Assignment by move. ==========\n";
        Value::flag = true;
        Value v0(0);
        Value v1(1);
        std::cout << "v0: " << v0 << ", "
                  << "v1: " << v1 << "\n";

        v0 = std::move(v1);
        std::cout << "v0: " << v0 << ", "
                  << "v1: " << v1 << "\n";

        v0 = create_value(2);
        std::cout << "v0: " << v0 << "\n";

        std::cout << "Initialization assignment. \n";
        Value v2 = create_value(3);
        std::cout << "&v2 = " << &v2 << "\n";
        std::cout << "v2: " << v2 << "\n";

        Value::flag = false;
        std::cout << "\n";
    }

    return 0;
}

程序的输出如下:

Hello, TryMoveSemantics! 
========== Swap 2 plain arrays. ==========
array0 0x7ffe93aba190: 0x7ffe93aba190 0, 0x7ffe93aba194 1, 
array1 0x7ffe93aba198: 0x7ffe93aba198 2, 0x7ffe93aba19c 3, 
array0 0x7ffe93aba190: 0x7ffe93aba190 2, 0x7ffe93aba194 3, 
array1 0x7ffe93aba198: 0x7ffe93aba198 0, 0x7ffe93aba19c 1, 

========== Swap 2 plain arrays of objects. ==========
array0 0x7ffe93aba190: 0x7ffe93aba190 val = 0, 0x7ffe93aba194 val = 1, 
array1 0x7ffe93aba198: 0x7ffe93aba198 val = 2, 0x7ffe93aba19c val = 3, 
array0 0x7ffe93aba190: 0x7ffe93aba190 val = 2, 0x7ffe93aba194 val = 3, 
array1 0x7ffe93aba198: 0x7ffe93aba198 val = 0, 0x7ffe93aba19c val = 1, 

========== Return std::vector by move. ==========
Creation and initialization. 
Move constructor. 
&valuesLocal = 0x7ffe93aba170
valuesLocal.data() = 0x55de50ea92a0
&valuesLocal[0] = 0x55de50ea92a0
&valuesLocal[1] = 0x55de50ea92a4
&values = 0x7ffe93aba170
values.data() = 0x55de50ea92a0
&values[0] = 0x55de50ea92a0
&values[1] = 0x55de50ea92a4
values: 
val = 0, val = 1, 

========== Separate creation and assignment. ==========
Separated creation and initialization. 
values: 
val = 2, val = 3, 
Move constructor. 
&valuesLocal = 0x7ffe93aba130
valuesLocal.data() = 0x55de50ea92c0
&valuesLocal[0] = 0x55de50ea92c0
&valuesLocal[1] = 0x55de50ea92c4
&values = 0x7ffe93aba110
values.data() = 0x55de50ea92c0
&values[0] = 0x55de50ea92c0
&values[1] = 0x55de50ea92c4
values: 
val = 0, val = 1, 
&valuesLong = 0x7ffe93aba150
valuesLong.data() = 0x55de50ea92a0
valuesLong: 
val = 2, val = 3, val = 4, 
Move constructor. 
&valuesLocal = 0x7ffe93aba170
valuesLocal.data() = 0x55de50ea92e0
&valuesLocal[0] = 0x55de50ea92e0
&valuesLocal[1] = 0x55de50ea92e4
&valuesLong = 0x7ffe93aba150
valuesLong.data() = 0x55de50ea92e0
&valuesLong[0] = 0x55de50ea92e0
&valuesLong[1] = 0x55de50ea92e4
valuesLong.size() = 2
values: 
val = 0, val = 1, 

========== Assignment by move. ==========
v0: val = 0, v1: val = 1
Move assignment. 
v0: val = 1, v1: val = 1
&temp = 0x7ffe93aba170
Move assignment. 
v0: val = 2
Initialization assignment. 
&temp = 0x7ffe93aba170
&v2 = 0x7ffe93aba170
v2: val = 3
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章