[C11] 能否使用move()回傳一個新的物件但不copy物件

最近花了很多時間研究是否能以rvalue reference在不複製物件的狀態下回傳新物件
但仍然找不到方法

一般講到這個問題 大家一定是會回答RVO(return value optimization)
雖然目前的compiler大部份都會做到RVO 但我們還是不能說RVO一定會進行

我天真的以為rvalue reference跟move可以讓function回傳物件的scope在return之後續繼維持
但其實是我誤會這個功能
move constructor/assignment的設計上還是要先construct物件
不論如何 move constructor還是會消耗新的空間與時間用在construct
move assignment也一定要先construct物件才能再assign
我們能處理的是member variable要怎麼複製

#include <iostream>
#include <memory> //shared_ptr
using namespace std;
class Foo {
public:
    int* i;
    Foo() {
        i = new int[1];
        i[1] = 1;
        cout << "default constructor" << endl;
    }
    ~Foo() {
        if (i != nullptr) {
            cout << "destructor, i[0]=" << i[0] << endl;
            delete[] i;
        } else {
            cout << "destructor, i is nullptr" << endl;
        }
    }
    Foo(Foo&& f) {
        cout << "move constructor, f.i[0]=" << f.i[0] <<  endl;
        i = f.i;
	//假設i是一個很大的array我們可以省掉copy array的時間
	//但若i是一個物件 而且我們有很多個member variable如i, j ,k ,l ,m, n
	//move constructor還是要先construct每個member varialbe
        f.i = nullptr;
    }
    Foo& operator=(Foo&& f) {
        cout << "move assigment, f.i[0]=" << f.i[0] <<  endl;
        i = f.i;
        f.i = nullptr;
        return *this;
    }
};
Foo factory() {
    Foo f;
    f.i[0] = 9;
    //do something for Foo
    return std::move(f); //will call move contrcutor
}

Foo RVO_factory() {
    Foo f;
    f.i[0] = 8;
    return f;
}

Foo fail_RVO_factory(bool b) {
    if (b) {
        Foo f;
        f.i[0] = 2;
        return f;
    } else {
        Foo f;
        f.i[0] = 3;
        return f;
    }
}

shared_ptr<Foo> factory_shared_ptr() {
    shared_ptr<Foo> shared_f = make_shared<Foo>();
    shared_f->i[0] = 99;
    //do something for Foo
    return shared_f;
}
int main() {
    {
        Foo foo = move(Foo());//will call move constructor
        foo = move(Foo());//will call move assigment
    }
    cout << "===========================" << endl;
    {
        cout << "call factory" << endl;
        Foo f = factory();
        cout << "after factor, f.i[0]: " << f.i[0] << endl;
    }
    cout << "===========================" << endl;
    {
        cout << "call RVO factory" << endl;
        Foo f = RVO_factory();
        cout << "after RVO factor, f.i[0]: " << f.i[0] << endl;
    }
    cout << "===========================" << endl;
    {
        cout << "call fail RVO factory" << endl;
        Foo f = fail_RVO_factory(true);
        cout << "after fail RVO factor, f.i[0]: " << f.i[0] << endl;
    }
    cout << "===========================" << endl;
    {
        shared_ptr<Foo> shared_f = factory_shared_ptr();
        cout << "get shared_f.i[0]: " << shared_f->i[0] << endl;
    }
    cout << "exit shared_f scope" << endl;

    return 0;

在我的機器上的執行結果:

default constructor
move constructor, f.i[0]=1
destructor, i is nullptr
default constructor
move assigment, f.i[0]=1
destructor, i is nullptr
destructor, i[0]=1
===========================
call factory
default constructor
move constructor, f.i[0]=9
destructor, i is nullptr
after factor, f.i[0]: 9
destructor, i[0]=9
===========================
call RVO factory
default constructor
after RVO factor, f.i[0]: 8
destructor, i[0]=8
===========================
call fail RVO factory
default constructor
move constructor, f.i[0]=2
destructor, i is nullptr
after fail RVO factor, f.i[0]: 2
destructor, i[0]=2
===========================
default constructor
get shared_f.i[0]: 99
destructor, i[0]=99
exit shared_f scope

現在大部份的編譯器都應該有RVO
所以在call RVO factory時應該都只會construct一次

但RVO目前還是有限制
若是return的物件被包在if之類的條件中
編譯器沒辦法的編譯期確認回傳的物件是哪個
這樣就沒辦法做到RVO

總結來說
除非對自己處理記憶體空間很有自信
目前應該還是要優先考慮使用shared pointer
讓編譯器幫我們處理空間問題

而move就算了 若Foo不是我們寫的物件 我們能做的事情更少
假如寫Foo的人沒有寫move或是沒寫好 反而會造成更大的問題…

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *