Home Item 3 (2/2) - 移動語意(move semantics)(中文)
Post
Cancel

Item 3 (2/2) - 移動語意(move semantics)(中文)

why std::move

全網最詳細介紹 std::move (自認啦)

當我們的 project 選擇使用 c++ 實作時,常常是因為 c++ 的執行效率很好,若在硬體資源較不足的情況下, ex: 嵌入式系統, 任何可以被優化的部分都要被優化。而 std::move 也是效率優化的一部分,特別是用在物件的拷貝上。 std::move 是 C++ 標準庫中的一個函數模板,定義在 <utility> 頭文件中。它的主要作用是將一個對象的所有權從一個變量轉移到另一個變量,而不進行昂貴的深拷貝。這在優化性能,尤其是涉及大量數據時非常有用。

我們先來介紹何謂深拷貝、何謂淺拷貝? 後面講到 std::move 就更清楚了。 先預告,這篇內容較多。

深拷貝 VS 淺拷貝

先說結論:

深拷貝:獨立的資源副本,新舊對象之間完全獨立,適用於需要保證數據獨立性的場景。 淺拷貝:資源共享,新舊對象共享相同的資源,可能導致資源管理問題(如重複釋放)。

深拷貝

直接看 code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <cstring>

class DeepCopy
{
private:
    int* data;
    int len;
public:
    DeepCopy(int len)
    {
        auto data = new int[len] {1,2,3,4,5};
        std::cout << "address: " << data << std::endl;
        this->data = data;
        this->len = len;
    }
    void print()
    {
        std::cout << "in print function: " << std::endl;
        std::cout <<"address: " << data << std::endl;
        std::cout << "data " << std::endl;

        for (size_t i = 0; i < len; i++)
        {
            std::cout << data[i] << " ";
        }
        std::cout << std::endl;
    }

    DeepCopy(const DeepCopy& other) {
        len = other.len;
        data = new int[other.len];
        memcpy(data, other.data, sizeof(int) * other.len);
    }

    ~DeepCopy()
    {
        delete[] data;
    }
};

int main()
{

    DeepCopy dc(5);
    dc.print();

    DeepCopy dc2 = dc;
    dc2.print();

    return 0;
}

Desktop View

上述可以直接看到兩個物件的 data 的 address 是不同的,因此不論 data 如何改變,皆不影響另一個物件內的 data 內容。

淺拷貝

這個例子會示範淺拷貝的問題,也就是資源重複釋放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
#include <cstring>

class ShallowCopy
{
private:
    int* data;
    int len;
public:
    ShallowCopy(int len)
    {
        auto data = new int[len] {1, 2, 3, 4, 5};
        std::cout << "address: " << data << std::endl;
        std::cout << data << std::endl;
        this->data = data;
        this->len = len;
    }
    void print()
    {
        std::cout << "in print function: " << std::endl;
        std::cout << "address: " << data << std::endl;
        std::cout << "data " << std::endl;
        if (data != nullptr)
        {
            for (size_t i = 0; i < len; i++)
            {
                std::cout << data[i] << " ";
            }
        }
        std::cout << std::endl;
    }

    ~ShallowCopy()
    {
        delete[] data;
    }
};

int main()
{

    ShallowCopy sc(5);
    sc.print();
    ShallowCopy sc2 = sc;
    sc2.print();

    return 0;
}

Desktop View

因兩個物件內部的 data 指向同一個位置,在物件超出 scope 要被回收時,call destructor 造成該位置記憶體被 delete 兩次。

1
2
3
4
~ShallowCopy()
    {
        delete[] data;
    }

BTW, 一般物件沒有特別去寫拷貝構造函數, default 就是淺拷貝。這要特別注意。

std::move

std::move 終於出場了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <cstring>

class ShallowCopy
{
private:
    int* data;
    int len;
public:
    ShallowCopy(int len)
    {
        auto data = new int[len] {1, 2, 3, 4, 5};
        std::cout << "address: " << data << std::endl;
        std::cout << data << std::endl;
        this->data = data;
        this->len = len;
    }
    void print()
    {
        std::cout << "in print function: " << std::endl;
        std::cout << "address: " << data << std::endl;
        std::cout << "data " << std::endl;
        if (data != nullptr)
        {
            for (size_t i = 0; i < len; i++)
            {
                std::cout << data[i] << " ";
            }
        }
        std::cout << std::endl;
    }

    ShallowCopy(ShallowCopy&& other) noexcept : data(other.data), len(other.len) {
        other.data = nullptr;
        other.len = 0;
        std::cout << "Moved" << std::endl;
    }

    ~ShallowCopy()
    {
        delete[] data;
    }
};

int main()
{

    ShallowCopy sc(5);
    sc.print();
    ShallowCopy sc2 = std::move(sc);
    sc2.print();

    return 0;
}

Desktop View

資源重複釋放的問題消失了。

結論:

  1. 淺拷貝可能會導致資源重複釋放問題。
  2. 深拷貝避免了資源重複釋放問題,但可能會有性能瓶頸。
  3. 移動語義通過資源轉移避免了淺拷貝的資源重複釋放問題,同時避免了深拷貝的性能瓶頸。

使用淺拷貝 & 深拷貝 -> 必須實做拷貝構造函數 語法為:

1
MyClass(const MyClass& other) {...your code...}

若沒有特別實作 -> default 為淺拷貝 使用 std::move -> 必須實作移動構造函數 語法為:

1
MyClass(const MyClass&& other) {...your code...}

可自己複製上面的 code 執行看看,更有感覺。 好拉,終於講完了。

☝ツ☝

This post is licensed under CC BY 4.0 by the author.

👈 ツ 👍