前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++10中的移动语义

C++10中的移动语义

作者头像
卡尔曼和玻尔兹曼谁曼
发布2019-01-22 11:02:19
4460
发布2019-01-22 11:02:19
举报

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

代码语言:javascript
复制
T clone(const T& rhs)
{
    T other = rhs;
    return other;
}

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

代码语言:javascript
复制
class Example
{
public:
    Example();
    Example(const Example& other);
    ~Example();

    Example clone();

public:
    int count;
    int* pNumber;
};
代码语言:javascript
复制
#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对象赋予移动语义: 添加移动构造函数和移动赋值运算符重载函数:

代码语言:javascript
复制
Example(Example&& other);
Example& operator=(Example&& rhs);
代码语言:javascript
复制
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;
}

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

代码语言:javascript
复制
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函数,这是一个经典的使用移动语义提高性能的示例。下面的是没有使用移动语义的函数:

代码语言:javascript
复制
void swap(T& a, T& b)
{
    T temp = a;
    a = b;
    b = temp;
}

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

代码语言:javascript
复制
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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年01月13日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档