c++11学习笔记(4)- 移动构造函数与移动语义

原创
2022-11-23
4724
5

1. 移动构造函数

什么是移动构造函数呢?

我们首先来看一个例子:

#include <iostream>
#include <stdlib.h>

class Object
{
public:
    Object() {
        std::cout << "Object()" << std::endl;
        m_pMem = new char[10];
        memset(m_pMem, 0, 10);
    }
    ~Object(){
        delete m_pMem;
    }

    Object(const Object& object) {
        std::cout << "Object(const Object&)" << std::endl;
        m_pMem = new char[10];
        memcpy(m_pMem, object.m_pMem, 10);
    }

    Object getObject(void) {
        Object obj;
        return obj;
    }

private:
    char* m_pMem = nullptr;
};

int main(int argc, char** argv)
{
    Object o1;
    Object o2 = o1.getObject();

    system("pause");
}

该示例的运行结果为:
Object()
Object()
Object(const Object&)

代码main()函数中 Object o1; 执行了一次普通构造函数;函数 getObject 也执行了一次普通构造函数并生成一个临时的 Object 对象,这里由于编译器的优化,这个临时对象就是函数中的 obj 本身; o2 对象的创建执行了一次拷贝构造函数。

如果我们能够把函数 getObject 产生的临时对象直接赋值给 o2 那该有多好啊,这样就可以不用使用拷贝构造再重新生成内存了,提升了程序的效率。那么有没有什么办法实现呢,当然是有的,使用移动构造函数,我们在上面的类 Object 中添加如下代码:

Object(Object&& object) {
    std::cout << "Object(Object&& object)" << std::endl;
    m_pMem = object.m_pMem;
    object.m_pMem = nullptr;
}

使用移动构造函数 Object(Object&& object) 将临时对象的堆内存转移到对象o2中,并使用代码object.m_pMem = nullptr; 以便object临时对象能够安全的被析构。
Object&& 实际上上一个 右值引用 ,关于右值引用可以参考上一篇文章
左值、右值和右值引用

在C++中这样“偷走”临时变量中资源的构造函数,就叫做 移动构造函数 ;而这样“偷”的行为就叫做 移动语义。移动语义在资源共享时,可以起到关键的作用。

2. std::move

在c++11中,标准库在 中提供了一个有用的函数 std::move,这个函数并不移动任何东西,他唯一的功能是将一个左值强制转化为右值引用,以用于移动语义

值得一提的是,被转化的左值,其生命周期并没有随着左右值得转化而改变。

我们将上面的代码改写一下:

#include <iostream>
#include <stdlib.h>

class Object
{
public:
    Object() {
        std::cout << "Object()" << std::endl;
        m_pMem = new char[10];
        memset(m_pMem, 0, 10);
    }
    ~Object(){
        delete m_pMem;
    }

    Object(const Object& object) {
        std::cout << "Object(const Object&)" << std::endl;
        m_pMem = new char[10];
        memcpy(m_pMem, object.m_pMem, 10);
        m_Number = object.m_Number;
    }

    Object(Object&& object) {
        std::cout << "Object(Object&& object)" << std::endl;
        m_pMem = object.m_pMem;
        object.m_pMem = nullptr;
        m_Number = object.m_Number;
    }

    Object getObject(void) {
        Object obj;
        return obj;
    }

    int m_Number = 10;

private:
    char* m_pMem = nullptr;
};

int main(int argc, char** argv)
{
    Object o1;
    Object o2 = std::move(o1);

    std::cout << o1.m_Number << std::endl;

    system("pause");
}

运行结果为
Object()
Object(Object&& object)
10

可见 o1 并没有因为左右值得转换而被释放。

有了移动语义,我们可以实现一个高效率的置换(swap)函数,代码如下:

template<typename T>
void swap(T& t1, T& t2) 
{
    T temp(std::move(t1));
    t1 = std::move(t2);
    t2 = std::move(temp);
}

如果T是可移动的,那么移动构造和移动赋值将用于这个替换,提高交换的效率;如果T是不可移动的确是可拷贝的,那么拷贝语义将会用于这个置换。

不会飞的纸飞机
扫一扫二维码,了解我的更多动态。

下一篇文章:c++11学习笔记(5)- 引用折叠和完美转发