构造函数,移动语义move与右值引用

发布时间 2023-08-05 23:08:55作者: Jeffxue

构造函数

C++的构造函数包含一般构造函数,拷贝构造函数与移动构造函数。

拷贝构造函数

  1. 其中包含浅拷贝和深拷贝(此处以深拷贝为例),主要是通过将已存在的对象的所有成员拷贝给新对象,来实现对新对象的初始化。这样就会存在两个一样的对象,相当于内存中存在两份。
  2. 拷贝构造函数的参数是一个左值引用,即为该类对象的常引用

移动构造函数

  1. 移动构造函数通过将已存在对象的资源转移给新对象(资源控制权的转移),来实现对新对象的初始化,而不必进行拷贝。此时旧对象资源就会转移给新对象,而旧对象就成为右值,将亡值。此时内存中该对象也只存在一份。
  2. 移动构造函数的形参是一个右值引用,其形式为 T&& 形式,其中T为类型。
  3. 对于使用临时的对象来构造新的对象,且后续不会再使用该临时对象,此时更适合使用移动构造函数。因为移动构造函数让新对象直接接管和使用临时对象的资源以及内存空间,来实现初始化,避免了重新分配内存空间进拷贝操作,提高了新对象的初始化效率,同时节省不必要的内存。
  4. 可以通过 std::move()函数来实现对移动构造函数的调用。因为该函数可以将左值变成右值引用。
class A 
{
public:
    // 构造函数
    A(int val1) 
    {
        printf("Constructor......\n");
        num1 = val1;
    }

    // 拷贝构造函数
    A(const A& obj): num1(obj.num1)
    {
        printf("Copy Constructor +++++\n");
    }


    // 移动构造函数
    A(A&& obj)
    {
        printf("Move Constructor ====\n");
        num1 = obj.num1;
    }
    
private:
    int num1;

};

int main()
{
    int val = 10; // val为左值

    A a1(val); // 调用构造函数
    A a2 = a1; // 调用拷贝构造函数,a2与a1一样,a1相当于a2的一个副本
    A a4(a1);  // 调用拷贝构造函数,传入的参数相当于对象的左值引用
    A a4(std::move(a2));// 调用移动构造函数, a2的资源将被转移给a4,避免拷贝副本
}

运行结果如下图所示,根据传入的参数类型,来调用对应的构造函数。


std::move() 函数

std::move()函数主要是用来实现语义转换,可以将左值转换成右值,调用移动构造函数来实现对新对象的初始化。
STL中的容器大部分都已经有移动构造函数的实现,vector::push_back为例,其有拷贝构造函数和移动构造函数两种实现,如下图:

int main()
{
    string str1 = "hello just a test";
    vector<string> vec;

    // 调用拷贝构造函数
    vec.push_back(str1);
    std::cout <<"Copy push, str1 = "<< std::quoted(str1) << endl;

    // 调用移动构造函数后,旧对象str1将被释放
    vec.push_back(std::move(str1));
    std::cout << "Move push, str1 = " << std::quoted(str1) << endl;
}

结果如下图,在调用拷贝构造函数之后,相当于Copy了一个副本,此时 str中任然是之前的值。而调用移动构造函数之后,旧对象str的资源及所有权都转移到新的对象中,在此处即为vector容器的元素,然后旧对象str作为一个将亡值就被释放了,此时打印的旧对象值就是空的。


【参考资料】

  1. What is std::move(), and when should it be used and does it actually move anything?