C++右值引用的示例

解釋

通常來說左值是指非零時對象,而右值是指臨時對象。我們知道c++在進行臨時對象的拷貝時會調用拷貝構造函數:

vector<string> v;
v.push_back(string("1234");

產生的行爲就是先構造一個“1234”的臨時string,然後調用拷貝拷貝構造函數拷貝臨時“1234”到vector裏面,然後銷燬臨時的“1234”。在這樣的語義裏面臨時“1234”明顯是一個右值,想一想它從產生到銷燬居然就是爲了被拷貝一次?那不等價於直接拿它的內容去用就好了嘛,是的c++的右值引用的出現幫我們解決了這個問題,我們看個栗子:

沒有用右值引用的栗子

#include <iostream>
#include <vector>
using namespace std;

extern "C" {
#include <string.h>
}

class MyClass {
    private:
        char* str;
    public:
        MyClass(const char* s) {
            cout<<"construct MyClass\n";
            str = new char[strlen(s)];
            strcpy(str, s);
        }

        ~MyClass() {
            cout<<"~MyClass\n";
            delete str;
        }

        MyClass(const MyClass& mc) {
            cout<<"copy MyClass\n";
            delete str;
            str = new char[strlen(mc.str)];
            strcpy(str, mc.str);
        }

        MyClass& operator= (const MyClass& mc) {
            cout<<"operator=(&)\n";
            if(this != &mc) {
                delete str;
                str = new char[strlen(mc.str)];
                strcpy(str, mc.str);
            }
        }

        friend ostream& operator << (ostream& os, MyClass& mc) {
            os<<mc.str;
            return os;
        }
};

vector<MyClass> makeMcVec() {
    vector<MyClass> v;
    v.reserve(3);
    v.push_back(MyClass("Hello World"));
    v.push_back(MyClass("\n"));
    for(auto it = v.begin(); it != v.end(); it++) {
        cout<<*it;
    }
    return v;
}

輸出爲:

construct MyClass
copy MyClass
~MyClass
construct MyClass
copy MyClass
~MyClass
Hello World
after makeMcVec
~MyClass
~MyClass

加入右值引用的區分

我們看到我們爲了用兩個string,產生了四次堆區的new和delete,把堆拷貝一次然後銷燬,那爲什麼不直接用就好了呢,這明顯性能不好,其實我們只要兩次就可以了。好我們加入兩個成員函數:

        MyClass& operator = (MyClass&& mc) {
            cout<<"operator = &&\n";
            if(this != &mc) {
                this->str = mc.str;
                mc.str = NULL;
            }
        }

        MyClass(MyClass&& mc) {
            cout<<"MyClass &&\n";
            this->str = mc.str;
            mc.str = NULL;
        }

輸出:

construct MyClass
MyClass &&
~MyClass
construct MyClass
MyClass &&
~MyClass
Hello World
after makeMcVec
~MyClass
~MyClass

我們看到,拷貝構造函數不被調用到了,這裏發生了什麼呢,當我們聲明瞭“&&”右值引用符號之後,對待右值時會調用右值引用的函數,這樣子我們就能針對右值引用做處理了,我們把MyClass(MyClass&& mc)的形式稱爲轉移構造函數,我們看到我們對待str的時候是直接“轉移過來了”而不是再拷貝一次,這樣就不用再去重複分配一次堆內存了,高效了。當然編譯器不會自動幫我們產生一個拷貝構造函數的,因爲它無法知道我們想幹什麼,需要我們手動聲明。
你可能說那我直接在拷貝構造函數裏面直接轉移不就行了了嘛何必多此一舉,如果拷貝的不是一個右值而是一個左值呢,你直接拷過來別人還想用啊,這樣不就出問題了嘛,明顯對待左值這麼做不對,所以需要區分。

move

vector<MyClass> makeMcVec1() {
    vector<MyClass> v;
    v.reserve(3);
    MyClass a("Hello World");
    MyClass b("\n");
    v.push_back(a);
    v.push_back(b);
    return v;
}

輸出

construct MyClass
construct MyClass
copy MyClass
copy MyClass
~MyClass
~MyClass
after makeMcVec
Hello World
~MyClass
~MyClass

在這種情況下其實我們實際上a可以當作右值,因爲a以後也不會被用到了,但是編譯器無法推導,有沒有辦法讓a被當作右值對待呢,加入move轉變成右值:

vector<MyClass> makeMcVec1() {
    vector<MyClass> v;
    v.reserve(3);
    MyClass a("Hello World");
    MyClass b("\n");
    v.push_back(move(a));
    v.push_back(move(b));
    return v;
}

輸出

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