C++11移動語義與右值引用、移動構造函數與移動賦值運算符

C++11移動語義與右值引用、移動構造函數與移動賦值運算符



C++11新特性移動語義,編譯器RVO技術,右值引用
都是爲了解決臨時變量的問題,讓系統的開銷變小。

一.前言

C++11爲什麼要引入右值引用?
總的來說,是爲了減小臨時變量多次申請空間的開銷。
引入了右值引用之後編譯器就能區分左值和右值(也就是可以區分哪些是臨時變量),也就可以調用移動構造函數與移動賦值運算符,對右值進行移動而不是拷貝,這樣減小了拷貝帶來的開銷。

總結:
1.右值引用只能關聯右值
3.左值引用只能關聯左值
2.常量左值引用均可關聯


二.移動語義

1.左值與右值

void test0()
{
    int x = 10;

    /**********右值引用***************/
    int &&a = 5; //右值引用 右值
    cout << "a = " << a << endl;
    //int &&a2 = x; //右值引用 左值  ERROR!一個右值引用不能關聯左值

    /**********左值引用***************/
    const int &b = 5; //常量左值引用 右值
    cout << "b = " << b << endl;

    const int &b2 = x; //常量左值引用 左值
    cout << "b2 = " << b2 << endl;

    int &c = a; //左值引用 左值
    cout << "c = " << c << endl;

    //int &c2 = 1;//左值引用 右值 ERROR!
}

2.std::move 移動語義

move的功能是將一個左值變爲右值引用,同時將元素進行轉移(注意不是拷貝!)


void test1()
{

    int a = 5;
    int &&b = move(a);
    cout << a << endl;
    cout << b << endl;

    string s = "HelloWorld";
    printf("%p\n", &s);
    vector<string> vec;
    // vec.push_back(s);//copy
    vec.push_back(std::move(s)); //move, 這裏s指向空間的內容已經全部被移動走了,所以爲空
    printf("%p\n", &s);
    cout << s << endl;
}


3.移動構造函數 移動賦值運算符

移動構造函數,傳入的String類型被系統判定爲右值,所以調用移動構造函數,這個步驟相比拷貝構造函數,省卻了在構造函數中申請空間的開銷,極大的節省了時間。
步驟:
1.將this指針中的_pstr指向右值的_pstr;
2.將右值的_pstr指向爲空;
3.右值析構,由於右值的_pstr已經指向爲空了,所以this指向的_pstr指向的空間並不會被釋放。

注意:
將右值的_pstr指向爲NULL非常重要,不然會出錯!
因爲右值馬上就會進行析構,如果不指向爲空的話就會釋放本來應該保留的this->_pstr指向的空間!

同樣,移動賦值運算符也是相同的道理。

下面的例子採用的是String類的編寫,其中採用了移動構造函數與移動賦值運算符,如果想了解完整的String類編寫,可以參考另外一篇博文:

class String
{
public:
    String();
    String(const char *);
    String(const String &);
    String(String &&lhs); //移動構造函數
    ~String();
    String &operator=(const String &);
    String &operator=(const char *);
    String &operator=(String &&lhs); //移動賦值運算符
    friend std::ostream &operator<<(std::ostream &os, const String &s);
    friend std::istream &operator>>(std::istream &is, String &s);

private:
    char *_pstr;
};

String::String()
    : _pstr(new char('\0'))
{
    cout << "String()" << endl;
}

String::String(const char *pstr)
    : _pstr(new char[strlen(pstr) + 1])
{
    cout << "String(const char*)" << endl;
    memcpy(_pstr, pstr, strlen(pstr) + 1);
}

String::String(const String &lhs)
    : _pstr(new char[strlen(lhs._pstr) + 1])
{
    cout << "String(const String&)" << endl;
    strcpy(_pstr, lhs._pstr);
}

/*
*/
String::String(String &&lhs)
{
    cout << "String(String&&)" << endl;
    _pstr = lhs._pstr;
    lhs._pstr = NULL;
}

String::~String()
{
    cout << "~String()" << endl;
    if (_pstr)
    {
        delete[] _pstr;
        _pstr = NULL;
    }
}

String &String::operator=(const String &lhs)
{
    delete[] _pstr;
    _pstr = new char[strlen(lhs._pstr) + 1];
    memcpy(_pstr, lhs._pstr, strlen(lhs._pstr) + 1);
    return *this;
}

String &String::operator=(const char *s)
{
    delete[] _pstr;
    _pstr = new char[strlen(s) + 1];
    memcpy(_pstr, s, strlen(s) + 1);
    return *this;
}

//移動賦值運算符
String &String::operator=(String &&lhs)
{
    if (this != &lhs)
    {
        cout << "String& operator=(String&&)" << endl;
        _pstr = lhs._pstr;
        lhs._pstr = NULL;
    }
    return *this;
}

ostream &operator<<(ostream &os, const String &s)
{
    if (s._pstr)
    {
        os << s._pstr;
    }
    return os;
}

//測試移動構造函數
void test2(){
    vector<String> vec;
    vec.push_back(String("s1"));
    cout << vec[0] << endl;
}

//測試移動賦值運算符
void test3(){
    String s1("s1");
    String s2("s2");
    s1 = std::move(s2);
    cout << s1 << endl;
}



三.RVO(Return Value optimization)返回值優化

我們在編寫程序的過程中可能會構造很多臨時變量並返回,如:

Data func(){
    Data temp(1);
    return Data;
}

如果按正常步驟來的話應該是:調用構造函數,再調用複製構造函數,再調用析構函數;
這很明顯很費時,所以我們應該使用RVO技術,也就是改變一種寫法:

Data RVO() {
	return Data(1);
}

按照RVO的方法來,只需要調用一次構造函數就可以了。

下面是實現RVO優化技術的代碼:

class Data
{
public:
	explicit Data(int val = 0) : _val(val) { cout << "Data(int)" << endl; }

	~Data() { cout << "~Data()" << endl; }

	//複製構造函數
	Data(const Data &lhs)
	{
		_val = lhs._val;
		cout << "Data(const Data&)" << endl;
	}

	//重載=運算符
	Data& operator=(const Data& lhs)
	{
		_val = lhs._val;
		cout << "Data& operator=(const Data&)" << endl;
	}

private:
	int _val;
};

//不使用RVO技術優化 分別調用構造函數,複製構造函數,析構函數
Data func() {
	Data temp(1);
	return temp;
}

//使用RVO技術優化 直接調用構造函數
Data RVO() {
	return Data(1);
}


int main()
{
    Data d = func();
	return 0;
}

注意:關於函數func()在某些編譯器(如g++)會直接使用RVO技術導致只會調用一次構造函數,之後我改用VS2017後,編譯器就不會再自動使用RVO技術了

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