4. 移动构造和移动赋值函数

移动构造函数和移动赋值函数 是 C++11 引入的语法,它们的主要作用是高效地转移资源所有权,避免不必要的深度拷贝。

语法

class 类名 {
    public:
        类名(类名&& other) noexcept {...}  // 移动构造函数
        类名& operator=(类名&& other) {...}  // 移动赋值函数
};

说明:

  1. 移动构造函数是在使用一个右值来创建新对象时调用。
  2. 移动赋值成员函数是在使用一个右值来对一个已有的对象赋值时调用。

示例

编写一个动态数组的类 DynamicArray 用来存储整数数据。此数组使用push_back 追加数据,并可可以实现数组的拼接等操作。

// filename: move.cpp
#include <iostream>

using namespace std;

#define MIN_SIZE (8)
class DynamicArray {
    public:
        // 初始化动态数组
        DynamicArray() : data(NULL), count(0), max_count(MIN_SIZE) {
                cout << "无参数构造函数被调用" << endl;
            }
        DynamicArray(const DynamicArray &src)
            :data(NULL), count(src.count), max_count(src.max_count) {
                cout << "拷贝构造函数被调用" << endl;
                if (src.data) {  // 如果src有数据才分配内存
                    data = new int[max_count];
                }
                // 复制数据
                for (int i = 0; i < count; i++) {
                    data[i] = src.data[i];
                }
            }
        DynamicArray & operator = (const DynamicArray &src) {
            cout << "拷贝赋值函数被调用" << endl;
            if (0 == src.count) { // 没有数据需要复制
                if (data) {
                    delete [] data;
                    data = NULL;
                }
                count = 0;
            } else { // 需要复制
                if (NULL == data)
                    data = new int[src.max_count];
                count = src.count;
                max_count = src.max_count;
                for (int i = 0; i < count; i++) {
                    data[i] = src.data[i];
                }
            }
            return *this;
        }
        DynamicArray(DynamicArray &&src)
            :data(src.data), count(src.count), max_count(src.max_count) {
                cout << "移动构造函数被调用" << endl;
                src.data = NULL;
                src.count = 0;
            }
        DynamicArray & operator = (DynamicArray &&src) {
            cout << "移动赋值函数被调用" << endl;
            if (data)
                delete [] data;
            data = src.data;
            src.data = NULL;
            count = src.count;
            max_count = src.count;
            return *this;
        }
        ~DynamicArray() {
            if (data) {
                delete [] data;
            }
        }
        // 2.  添加数据
        void push_back(const int & x){
            // 需要重新分配内存
            if (data == NULL)  {  // 一定要分配内存
                data = new int[max_count];
            } else if (count >= max_count) { // 需要扩容
                int * temp = data;
                data = new int[max_count * 2];
                for (int i = 0; i < count; i++)
                    data[i] = temp[i];
                delete [] temp;
            }
            data[count] = x;
            count++;
        }

        DynamicArray operator+(const DynamicArray &r) {
            DynamicArray tmp;
            tmp.count = count + r.count; // 返回对象的元素个数
            // 计算 最大容量(最大容量是8的n次方)
            tmp.max_count = MIN_SIZE;
            while(tmp.max_count < tmp.count) 
                tmp.max_count *= 2;
            tmp.data = new int[tmp.max_count];
            // 复制每一个整数值
            int i;
            for (i = 0; i < count; i++) {//复制this数据
                tmp.data[i] = data[i];
            }
            //复制r的数据
            for (int j = 0; j < r.count; j++) {
                tmp.data[i+j] = r.data[j];
            }
            return tmp;
        }
        int size(void) { // 返回大小
            return count;
        }
    private:
        int * data;  // 用来保存数据的指针
        int count; // 用来记录数据的个数。
        int max_count;  // 用来记录当前内存能存储的最大数据
    private:
        friend ostream & operator<<(ostream &o, const DynamicArray & da);
};

ostream & operator<<(ostream &o, const DynamicArray & da) {
    o << "[";
    for (int i = 0; i < da.count; i++) {
        o << da.data[i];
        if ( i < da.count -1)
            o << ", ";
    }
    o << "]";
    return o;
}
int main(int argc, char * argv[]) {
    DynamicArray d1;
    for (int i = 11; i <= 33; i+= 11)
        d1.push_back(i);
    cout << d1 << endl;
    // 调用拷贝构造函数
    DynamicArray d2(d1);
    cout << d2 << endl;
    // 调用移动构造函数
    DynamicArray d3(d1 + d2);
    cout << d3 << endl;

    DynamicArray d4;
    // 调用移动赋值函数
    d4 = d1 + d2;
    cout << d4 << endl;

    return 0;
}

为了能看到效果,避免移动构造函数调用被优化,编译时可以使用如下的 g++ 选项

g++ -fno-elide-constructors -o xxx xxx.cpp

编译和运行结果如下:

weimz@mzstudio:~$ g++ -fno-elide-constructors -o move move.cpp
weimz@mzstudio:~$ ./move 
无参数构造函数被调用
[11, 22, 33]
拷贝构造函数被调用
[11, 22, 33]
无参数构造函数被调用
移动构造函数被调用
移动构造函数被调用
[11, 22, 33, 11, 22, 33]
无参数构造函数被调用
无参数构造函数被调用
移动构造函数被调用
移动赋值函数被调用
[11, 22, 33, 11, 22, 33]

一般,一个类内如果有指针成员变量,则此类在编写时一般多要准守三五法则

三五法则