C++ 移动语义

移动语义是 C++11 标准引入的一个特性,旨在通过优化资源管理改善 C++ 的效率。移动语义主要涉及通过右值引用(rvalue references)来避免不必要的深拷贝,从而减少资源的分配和释放。

1. 拷贝场景

在 C++ 中,有很多的编码场景下,会导致对象的拷贝(拷贝构造函数、拷贝赋值函数)。特别是针对临时对象即将废弃的对象的拷贝,将会极大降低程序的效率。

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

class Demo
{
public:
	Demo(int n)
	{
		m_size = n;
		p_arr = (int*)malloc(sizeof(int) * m_size);
		if (nullptr == p_arr)
		{
			return;
		}
		for (int i = 0; i < 10; ++i)
		{
			p_arr[i] = 100 + i;
		}
		cout << "默认构造" << endl;
	}

	Demo(const Demo& demo)
	{	
		m_size = demo.m_size;
		p_arr = (int*)malloc(sizeof(int) * m_size);
		if (nullptr == p_arr)
		{
			return;
		}

		for (int i = 0; i < 10; ++i)
		{
			p_arr[i] = demo.p_arr[i];
		}
		cout << "拷贝构造" << endl;
	}

	Demo& operator=(const Demo& demo)
	{
		if (this == &demo)
		{
			return *this;
		}

		m_size = demo.m_size;

		if (p_arr != nullptr)
		{
			delete[] p_arr;
			p_arr = nullptr;
		}

		p_arr = (int*)malloc(sizeof(int) * m_size);
		if (nullptr == p_arr)
		{
			return *this;
		}

		for (int i = 0; i < 10; ++i)
		{
			p_arr[i] = demo.p_arr[i];
		}
		cout << "拷贝赋值" << endl;

		return *this;
	}

	~Demo()
	{
		if (p_arr != nullptr)
		{
			delete[] p_arr;
			p_arr = nullptr;
		}
		cout << "析构函数" << endl;
	}

public:
	int* p_arr;
	int m_size;
};


Demo create_demo()
{
	Demo demo1(10);
	Demo demo2(15);

	if (demo1.m_size > demo2.m_size)
	{
		return demo1;
	}

	return demo2;
}


// 1. 返回局部对象
void test01()
{
	// create_demo 返回临时对象
	// 在销毁之前,需要调用拷贝构造函数
	Demo demo = create_demo();
}


// 2. 值方式传参
void do_logic(const Demo& demo) {}
void test02()
{
	// create_demo 返回临时对象
	// 在销毁之前,会创建临时对象赋值给 do_logic 的 demo 参数
	do_logic(create_demo());
}


// 3. 容器操作
void test03()
{
	vector<Demo> vec;
	// 将临时对象拷贝到容器中
	vec.push_back(Demo(10));
}


// 4. 交换两个对象
void test04()
{
	Demo demo1(10);
	Demo demo2(20);
	swap(demo1, demo2);
}


// 5. 对象赋值
void test05()
{
	Demo demo(10);

	// 将对象拷贝赋值给 demo 对象
	demo = create_demo();
}


// 6. 容器中元素赋值
void test06()
{
	vector<Demo> vec;
	vec.push_back(Demo(10));
	vec[0] = Demo(10);
}


int main()
{
	cout << "----test01----" << endl;
	test01();
	cout << "----test02----" << endl;
	test02();
	cout << "----test03----" << endl;
	test03();
	cout << "----test04----" << endl;
	test04();
	cout << "----test05----" << endl;
	test05();
	cout << "----test06----" << endl;
	test06();
	return 0;
}

#endif

程序执行结果:

----test01----
默认构造
默认构造
拷贝构造
析构函数
析构函数
析构函数
----test02----
默认构造
默认构造
拷贝构造
析构函数
析构函数
析构函数
----test03----
默认构造
拷贝构造
析构函数
析构函数
----test04----
默认构造
默认构造
拷贝构造
拷贝赋值
拷贝赋值
析构函数
析构函数
析构函数
----test05----
默认构造
默认构造
默认构造
拷贝构造
析构函数
析构函数
拷贝赋值
析构函数
析构函数
----test06----
默认构造
拷贝构造
析构函数
默认构造
拷贝赋值
析构函数
析构函数

如果我们能够将这些临时对象、或者即将废弃的对象所持有的资源进行转移、而不是拷贝,这将大大提高程序的效率。

2. 右值引用

要实现针对临时对象(右值)的特殊拷贝操作,首要一点,就是要区分左值(持久对象)对象和右值对象。在 C++11 之前,只有左值引用、常量引用(万能引用),无法精确匹配到右值场景。所以,在 C++11 中,增加了专门针对右值的右值引用,使用 && 语法表示,从而辅助实现资源移动语义。

#if 1
#include <iostream>
using namespace std;

class Demo
{
public:
	Demo(int n)
	{
		cout << "默认构造" << endl;
	}

	// 既能匹配左值对象、又能匹配右值对象
	// 即:无法区分左值、右值,也就无法实现分别处理
	Demo(const Demo& demo)
	{
		cout << "拷贝构造" << endl;
	}

	Demo& operator=(const Demo& demo)
	{
		cout << "拷贝赋值" << endl;
		return *this;
	}

	~Demo()
	{
		cout << "析构函数" << endl;
	}

public:
	int* p_arr;
	int m_size;
};

void test()
{
	// 左值引用
	Demo demo(10);
	Demo& r1 = demo;

	// 常量引用(万能引用)
	// Demo& r2 = Demo(20);  // 非常量引用的初始值必须为左值
	const Demo& r3 = Demo(20);

	// 右值引用
	Demo&& r4 = Demo(20);
	// Demo&& r5 = demo;  // 无法将右值引用绑定到左值
}


int main()
{
	test();
	return 0;
}


#endif

3. 资源移动

移动语义是一种机制,它允许开发者“移动”资源(如动态内存、文件句柄等),而不是复制资源。当对象的资源被移动时,源对象的资源所有权被转移到目标对象,源对象则被置于一种有效但未指定的状态。移动语义通过移动构造函数移动赋值运算符实现。为了能够匹配临时对象,可以在类中增加:

  1. 参数为右值引用的构造函数(移动构造函数)
  2. 参数为右值引用的赋值函数(移动赋值函数)
// 移动构造
Demo(Demo&& demo)

// 移动赋值
Demo& operator=(Demo&& demo)

在这两个函数编写资源转移的相关代码。

注意两点:

  1. 参数为右值引用,而非常量右值引用
  2. 如果一个右值不再使用,也可以使用 move 函数将左值转换为右值对象,实现资源转移
#if 1
#include<iostream>
#include <vector>
using namespace std;

class Demo
{
public:
    Demo(int n)
    {
        m_size = n;
        p_arr = (int*)malloc(sizeof(int) * m_size);
        if (nullptr == p_arr)
        {
            return;
        }
        for (int i = 0; i < 10; ++i)
        {
            p_arr[i] = 100 + i;
        }
        cout << "默认构造" << endl;
    }

    Demo(const Demo& demo)
    {
        m_size = 0;
        p_arr = nullptr;

        m_size = demo.m_size;
        p_arr = (int*)malloc(sizeof(int) * m_size);
        if (nullptr == p_arr)
        {
            return;
        }

        for (int i = 0; i < 10; ++i)
        {
            p_arr[i] = demo.p_arr[i];
        }
        cout << "拷贝构造" << endl;
    }

    Demo& operator=(const Demo& demo)
    {
        if (this == &demo)
        {
            return *this;
        }

        m_size = demo.m_size;

        if (p_arr != nullptr)
        {
            delete[] p_arr;
            p_arr = nullptr;
        }

        p_arr = (int*)malloc(sizeof(int) * m_size);
        if (nullptr == p_arr)
        {
            return *this;
        }

        for (int i = 0; i < 10; ++i)
        {
            p_arr[i] = demo.p_arr[i];
        }
        cout << "拷贝赋值" << endl;

        return *this;
    }

    /**************资源转移********************/
    Demo(Demo&& demo)
    {
        // 获得 demo 资源所有权
        p_arr = demo.p_arr;
        m_size = demo.m_size;

        // 移除 demo 资源所有权
        demo.p_arr = nullptr;
        demo.m_size = 0;

        cout << "移动构造" << endl;
    }

    Demo& operator=(Demo&& demo)
    {
        if (p_arr != nullptr)
        {
            delete[] p_arr;
            p_arr = nullptr;
            m_size = 0;
        }

        // 获得 demo 资源所有权
        p_arr = demo.p_arr;
        m_size = demo.m_size;

        // 移除 demo 资源所有权
        demo.p_arr = nullptr;
        demo.m_size = 0;

        cout << "移动赋值" << endl;

        return *this;
    }


    /**********************************/

    ~Demo()
    {
        if (p_arr != nullptr)
        {
            delete[] p_arr;
            p_arr = nullptr;
        }
        cout << "析构函数" << endl;
    }

public:
    int* p_arr;
    int m_size;
};

Demo create_demo()
{
    Demo demo1(10);
    Demo demo2(15);

    if (demo1.m_size > demo2.m_size)
    {
        return demo1;
    }

    return demo2;
}


// 1. 返回局部对象
void test01()
{
    // create_demo 返回临时对象
    // 在销毁之前,需要调用拷贝构造函数
    Demo demo = create_demo();
}


// 2. 值方式传参
void do_logic(const Demo& demo) {}
void test02()
{
    // create_demo 返回临时对象
    // 在销毁之前,会创建临时对象赋值给 do_logic 的 demo 参数
    do_logic(create_demo());
}


// 3. 容器操作
void test03()
{
    vector<Demo> vec;
    // 将临时对象拷贝到容器中
    vec.push_back(Demo(10));
}


// 4. 交换两个对象
void test04()
{
    Demo demo1(10);
    Demo demo2(20);
    swap(demo1, demo2);
}


// 5. 对象赋值
void test05()
{
    Demo demo(10);

    // 将对象拷贝赋值给 demo 对象
    demo = create_demo();
}


// 6. 容器中元素赋值
void test06()
{
    vector<Demo> vec;
    vec.push_back(Demo(10));
    vec[0] = Demo(10);
}

// 7. 即将销毁的对象
void test07()
{
    Demo demo(10);
    vector<Demo> vec;
    vec.push_back(move(demo));
    cout << "旧对象:" << demo.p_arr << " " << demo.m_size << endl;
}


int main()
{
    cout << "----test01----" << endl;
    test01();
    cout << "----test02----" << endl;
    test02();
    cout << "----test03----" << endl;
    test03();
    cout << "----test04----" << endl;
    test04();
    cout << "----test05----" << endl;
    test05();
    cout << "----test06----" << endl;
    test06();
    cout << "----test07----" << endl;
    test07();
    return 0;
}

#endif

未经允许不得转载:一亩三分地 » C++ 移动语义
评论 (0)

2 + 6 =