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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章