C++10中的移动语义

本文涉及的产品
实时计算 Flink 版,5000CU*H 3个月
实时数仓Hologres,5000CU*H 100GB 3个月
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: 对象的移动语义(Move Semantics)需要实现移动构造函数(move constructor)和移动赋值运算符(move assignment operator)。如果源对象是在复制或者赋值结束以后被销毁的临时对象,编译器会使用两种方法。移动构造函数和移动赋值运算符将成员变量从源对象复制/移动到新对象,然后将源对象的变量设置为空值。这样做实际上将内存的所有权从一个对象转移到另一个对象。这两种方法基本上只对成员变量进行浅拷贝(shallow copy),然后转换已分配内存的权限,从而防止悬挂指针和内存泄露。

首先,我们来看这样一个函数:
(T为一个对象类型)

T clone(const T& rhs)
{
    T other = rhs;
    return other;
}

这样的函数中,其实会调用两次拷贝构造函数。我们写一个实例看看:

class Example
{
public:
    Example();
    Example(const Example& other);
    ~Example();

    Example clone();

public:
    int count;
    int* pNumber;
};
#include "move_semantic.h"
#include <iostream>

using namespace std;

Example::Example()
{
    cout << "默认构造函数..." << '\n';
}

Example::Example(const Example& other)
{
    count = other.count;
    pNumber = new int[count];
    // 深拷贝
    for (int i = 0; i < count; i++)
        pNumber[i] = other.pNumber[i];

    cout << "拷贝构造函数..." << '\n';
}

Example::~Example()
{
    delete pNumber;
    cout << "这是析构函数..." << '\n';
}

Example Example::clone()
{
    Example demo(*this);
    return demo;
}

int main()
{
    Example demo;
    demo.count = 10;
    demo.pNumber = new int[demo.count];
    for (int i = 0; i < demo.count; i++)
        demo.pNumber[i] = i + 1;

    demo.clone();

    return 0;
}

执行结果如下:
拷贝构造函数
第一次默认拷贝构造函数的调用是在demo对象的初始化过程中;
两次拷贝构造函数实在clone函数的调用过程中:
clone函数中利用this对象初始化demo对象进行一个拷贝构造,然后返回demo对象。返回过程中会再次调用一次拷贝构造返回局部对象demo的一个拷贝。

如果利用C++11的移动语义(Move Semantics),则在clone函数返回的的时候,我们不是重新拷贝一个对象,而是将demo这个临时对象的所有权交给另外一个对象,这样避免了对象的拷贝,提高了效率。

对象的移动语义(Move Semantics)需要实现移动构造函数(move constructor)和移动赋值运算符(move assignment operator)。如果源对象是在复制或者赋值结束以后被销毁的临时对象,编译器会使用两种方法。移动构造函数和移动赋值运算符将成员变量从源对象复制/移动到新对象,然后将源对象的变量设置为空值。这样做实际上将内存的所有权从一个对象转移到另一个对象。这两种方法基本上只对成员变量进行浅拷贝(shallow copy),然后转换已分配内存的权限,从而防止悬挂指针和内存泄露。
移动语义是通过右值引用实现的。在C++中,左值是可以获取其地址的一个量,例如有名称的变量。由于经常出现在赋值语句的左边,因此称其为左值。所有不是左值的量都是右值,例如常量、临时变量或者临时对象。通常位于赋值运算符的右边。
右值引用是一个对右值的引用。特别地,这是一个当右值是临时对象时使用的概念。右值引用的目的是提供在临时对象时可选用的特定的方法。由于知道临时对象会被销毁,通过右值引用,某些涉及复制大量值的操作可以通过简单地复制指向指向这些值的指针实现。
函数可将&&作为参数说明的一部分(例如T&& name)来指定右值引用参数。

下面看如何对上面的Example对象赋予移动语义:
添加移动构造函数和移动赋值运算符重载函数:

Example(Example&& other);
Example& operator=(Example&& rhs);
Example::Example(Example&& other)
{
    count = other.count;
    pNumber = other.pNumber;

    other.count = 0;
    other.pNumber = nullptr;

    cout << "移动构造函数..." << '\n';
}

Example& Example::operator=(Example&& rhs)
{
    if (this == &rhs) return *this;

    count = rhs.count;
    delete pNumber;
    pNumber = rhs.pNumber;

    rhs.count = 0;
    rhs.pNumber = nullptr;

    cout << "移动赋值运算..." << '\n';

    return *this;
}

我们写个主函数进行测试:

int main()
{
    Example demo;
    demo.count = 10;
    demo.pNumber = new int[demo.count];
    for (int i = 0; i < demo.count; i++)
        demo.pNumber[i] = i + 1;


    Example other = demo.clone();
    other.pNumber[0] = 100;

    cout << demo.pNumber[0] << '\n';
    cout << other.pNumber[0] << '\n';

    return 0;
}

运行结果如下:
移动语义

个人感觉使用移动语义,主要是必满了临时对象的多次拷贝,提高了程序运行效率。

下面来看一个交换两个对象的swap函数,这是一个经典的使用移动语义提高性能的示例。下面的是没有使用移动语义的函数:

void swap(T& a, T& b)
{
    T temp = a;
    a = b;
    b = temp;
}

这种实现首先将a复制到temp,然后将b复制到a,最后将temp赋值到b。如果类型T的复制开销很大,这个交换实现严重影像性能。使用移动语义,swap函数可以避免所有的复制。

void swap(T& a, T& b)
{
    T temp = std::move(a);
    a = std::move(b);
    b = std::move(temp);
}

std::move可以将左值转换为右值。

参考资料:http://blog.csdn.net/niteip/article/details/37814483

目录
相关文章
C++11 右值引用和移动语义(三)
C++11 右值引用和移动语义
62 0
|
8月前
|
编译器 C++ 容器
【C++11特性篇】探究【右值引用(移动语义)】是如何大大提高效率?——对比【拷贝构造&左值引用】
【C++11特性篇】探究【右值引用(移动语义)】是如何大大提高效率?——对比【拷贝构造&左值引用】
|
存储 安全 编译器
【C++11新特性】右值引用和移动语义(移动构造,移动赋值)
【C++11新特性】右值引用和移动语义(移动构造,移动赋值)
|
8月前
|
存储 编译器 C++
【C++】—— C++11新特性之 “右值引用和移动语义”
【C++】—— C++11新特性之 “右值引用和移动语义”
|
8月前
|
存储 编译器
C++11(左值(引用),右值(引用),移动语义,完美转发)
C++11(左值(引用),右值(引用),移动语义,完美转发)
68 0
|
8月前
|
编译器 C++
c++左值、右值引用和移动语义
c++左值、右值引用和移动语义
77 0
|
8月前
|
安全 编译器 C++
c++11新特性——右值引用和move语义
c++11新特性——右值引用和move语义
|
8月前
|
编译器 C++
深入理解 C++ 右值引用和移动语义:全面解析
C++11引入了右值引用,它也是C++11最重要的新特性之一。原因在于它解决了C++的一大历史遗留问题,即消除了很多场景下的不必要的额外开销。即使你的代码中并不直接使用右值引用,也可以通过标准库,间接地从这一特性中收益。为了更好地理解该特性带来的优化,以及帮助我们实现更高效的程序,我们有必要了解一下有关右值引用的意义。
103 0
|
C++ 容器
C++新特性:右值引用,移动语义,完美转发
C++新特性:右值引用,移动语义,完美转发
68 0