1. 程式人生 > 實用技巧 >C++11中令人吐血的"移動語義"和"新的右值引用"

C++11中令人吐血的"移動語義"和"新的右值引用"

目錄

本文轉載自:https://blog.csdn.net/xuwqiang1994/article/details/79924310

1.概述

沒有C++11之前我都這樣寫程式碼的

Class* ptr = new Class(xxx);

感覺指標用的挺好的,邏輯清晰,很容易理解。
C++11出來之後,有了"移動語義"和"新的右值引用"的概念,可以預見以後看程式碼有多頭疼。

2.左值與右值

左值(lvalue):代表一個在記憶體中佔有確定位置的物件;
右值(rvalue):通過排他性來定義,不是lvalue的都屬於rvalue,即在不在記憶體中佔有確定位置的表示式;

2.1 例1:

int var;
var = 4;
4 = var;        //ERROR!
(var + 10) = 4; //ERROR!

如上, 顯然"4、(var + 10)"這些表示式是不能作為左值的。

2.2 例2:

int foo() { return 2; }
int main()
{
    foo() = 2; //ERROR
    return 0;
}

這裡的foo()也不能作為左值,而C++中的引用(reference)讓這成為可能

2.3 例3:

int globalvar = 20;
int& foo()
{
    return globalvar;
}
int main()
{
    foo() = 10;
    return 0;
}

這裡的"&"當表示引用含義的時候,修飾的需要是一個左值。

3.不用指標勝似指標的做法

3.1 疑問

拿C++11舉例,C++11裡引入了thread,下面的程式碼會不會讓你感到疑惑?

  std::thread threads[5];
  for (int i=0; i<5; ++i)
    threads[i] = std::thread(pause_thread,i+1);

請問thread的建構函式到底被呼叫了幾次呢?

3.2 樣例1

我們不考慮thread,看下面這個Intvec程式碼樣例:

class Intvec
{
public:
    explicit Intvec(size_t num = 0)
        : m_size(num), m_data(new int[m_size])
    {
        log("constructor");
    }

    ~Intvec()
    {
        log("destructor");
        if (m_data) {
            delete[] m_data;
            m_data = 0;
        }
    }

    Intvec(const Intvec& other)
        : m_size(other.m_size), m_data(new int[m_size])
    {
        log("copy constructor");
        for (size_t i = 0; i < m_size; ++i)
            m_data[i] = other.m_data[i];
    }

    Intvec& operator=(const Intvec& other)
    {
        log("copy assignment operator");
        Intvec tmp(other);
        std::swap(m_size, tmp.m_size);
        std::swap(m_data, tmp.m_data);
        return *this;
    }
	
private:
    void log(const char* msg)
    {
        cout << "[" << this << "] " << msg << "\n";
    }

    size_t m_size;
    int* m_data;
};
int main()
{
    Intvec v2;
    cout << "assigning lvalue...\n";
    v2 = Intvec(20);
    cout << "ended assigning lvalue...\n";
    return 0;
}

執行結果

$ g++ main2.cpp -o main2 --std=c++11
$ ./main2
[0x7ffe204edc50] constructor
assigning lvalue...
[0x7ffe204edc60] constructor
[0x7ffe204edc50] copy assignment operator
[0x7ffe204edc20] copy constructor
[0x7ffe204edc20] destructor
[0x7ffe204edc60] destructor
ended assigning lvalue...
[0x7ffe204edc50] destructor

結果呼叫3次建構函式:
Intvec v2;
Intvec(20);
Intvec tmp(other);

3.3 樣例2:

我們再加一個成員函式;(不用刪除原來的operator=)

    Intvec& operator=(Intvec&& other)
    {
        log("move assignment operator");
        std::swap(m_size, other.m_size);
        std::swap(m_data, other.m_data);
        return *this;
    }

執行結果:

$ g++ main2.cpp -o main2 --std=c++11
$ ./main2
[0x7ffe5aa0ad70] constructor
assigning lvalue...
[0x7ffe5aa0ad80] constructor
[0x7ffe5aa0ad70] move assignment operator
[0x7ffe5aa0ad80] destructor
ended assigning lvalue...
[0x7ffe5aa0ad70] destructor

結果只調用了兩次建構函式。
從外觀上看
Intvec& operator=(Intvec&& other)和
Intvec& operator=(const Intvec& other)
在傳參上並沒有什麼不同。但顯然編譯器知道自己該呼叫哪個函式。

4.總結

"&&"就是C++11支援的"新右值引用操作符",operator=(Intvec&& other)這個函式就是實現"移動語義"的一種方法。

PS:C++越改越像個指令碼語言,圖啥?
從個人角度看,以後寫程式碼,我還是傾向於使用

Intvec* p2 = new Intvec(20);
delete p2;

的方式,這隻呼叫一次建構函式,而且邏輯還很清晰,不用考慮類內部的實現。

Intvec v2 = Intvec(20); //也只調用一次,這就是另外一回事了。

本文轉載自:https://blog.csdn.net/xuwqiang1994/article/details/79924310