C++11新特性(侯捷)——课程笔记(九)

本部分的内容包括右值引用(rvalue reference)以及move语义的初步介绍。

一、Rvalue references

Rvalue references are a new reference type introduced in C++ that help solve the problem of unnecessary copying and enable perfect forwarding. When the right-hand side of an assignment is an rvalue, then the left-hand side object can steal resources from the right-hand side object rather than performing a seperate allocation, thus enabling move semantics.

(注:perfect forwarding被翻译为精确传递,精确转发等,其含义为将一组参数原封不动地传递给另一个函数。在这里“原封不动”不仅仅是参数的值不变,除了参数值之外,还有两组属性:左值 / 右值和 const / non-const, 精确传递就是在参数传递过程中,所有这些属性和参数值都不能改变。第二部分也会具体解释。另参见https://blog.csdn.net/jxianxu/article/details/62046839)

1. 左值和右值的定义

lvalue:可以出现在operator=左边的值;rvalue:只能出现在operator=右边的值。

比如说临时对象就是一种右值,形似:

int a = 0, b = 0;
a + b = 4;

不能通过编译。但例外的是,很多class的临时对象竟然可以被赋值,以string为例:

string s1("Hello ");
string s2("World");
s1 + s2 = s2;            //竟然可以通过编译
//赋值之后s1, s2的值不变
string() = "World";      //竟然可以对temp obj赋值

但是这只是一种例外情况,可以理解为语言本身的“bug”,临时对象是永远被当做右值的,即使有些class的临时对象可以放在等号左边(“可以”指可以通过编译,给临时对象赋值无意义)。

2. 为什么要引入一个新的"右值引用"的概念

当rvalue出现于operator=(copy assignment)的右侧,我们认为对其资源进行偷取 / 搬移(move)而非拷贝(copy)是可以的,是合理的,那么:

(1)必须有语法让我们在调用端告诉编译器:这是个rvalue

(2)必须有语法让我们在被调用端写出一个专门处理rvalue的所谓move assignment函数

3. 一个例子

(1)insert()往容器中插入元素,涉及到元素的移动,所以为了效率insert()有右值引用的版本,搬动元素的时候要调用元素的ctor,调用的ctor就是元素的move ctor。

(2)一个对象的move ctor的逻辑:简单地copy 指针的值,所以原来的对象对资源的引用要销毁,要保证原来的对象不再使用,这样才安全

(3)使用std::move()可以得到一个左值的右值引用

二、Perfect forwarding

这里只说了怎么写可以实现完美转发,但是没有解释为什么需要这么写?以及各种不同类型(左值还是右值,const还是non-const)的情况是怎样得到正确处理的?得去别的地方找一下。

下面是标准库中forward()和move()的定义:

//Forward on lvalue
template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); }

//Forward on rvalue
template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
    { 
        static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
                " substituting _Tp is an lvalue reference type");
        return static_cast<_Tp&&>(__t); 
    }
template<typename _Tp>
    constexpr typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

三、写一个move-aware class

class MyString{
public:
    static size_t DCtor;        //累计default-ctor呼叫次数
    static size_t Ctor;         //累计ctor呼叫次数
    static size_t CCtor;        //累计copy-ctor呼叫次数
    static size_t CAsgn;        //累计copy-asgn呼叫次数
    static size_t MCtor;        //累计move-ctor呼叫次数
    static size_t MAsgn;        //累计move-asgn呼叫次数
    static size_t Dtor;         //累计dtor呼叫次数
private:
    char* _data;
    size_t _len;
    void init_data(const char *s){
        _data = new char[_len + 1];
        memcpy(_data, s, _len);
        _data[_len] = '\0';
    }
public:
    //default constructor
    MyString() : _data(NULL), _len(0) { ++DCtor; }

    //constructor
    MyString(const char* p) : _len(strlen(p)){
        ++Ctor;
        _init_data(p);
    }

    //copy constructor
    MyString(const MyString& str) : _len(str.len){
        ++CCtor;
        _init_data(str._data);
    }

    //move constructor, with "noexcept"
    MyString(MyString&& str) noexcept
        : _data(str._data), _len(str._len){
            ++MCtor;
            str._len = 0;
            str.data = NULL;        //重要
    }

    //copy assignment
    MyString& operator=(const MyString& str){
        ++CAsgn;
        if(this != &str){
            if(_data) delete _data;
            _len = str._len;
            _init_data(str._data);
        }
        else{
        }
        return *this;
    }

    //move assignment
    MyString& operator=(MyString&& str) noexcept{
        ++MAsgn;
        if(this != &str){
            if(_data) delete _data;
            _len = str._len;
            _data = str._data;
            str._len = 0;
            str._data = NULL;
        }
        return *this;
    }

    //dtor
    virtual ~MyString(){
        ++Dtor;
        if(_data){
            delete _data;
        }
    }

    bool
    operator<(const MyString& rhs) const
    {
        return string(this->_data) < string(rhs._data);
    }

    bool
    operator==(const MyString& rhs) const
    {
        return string(this->_data) == string(rhs._data);
    }

    char* get() const { return _data; }
};
size_t MyString::DCtor = 0;
size_t MyString::Ctor = 0;
size_t MyString::CCtor = 0;
size_t MyString::CAsgn = 0;
size_t MyString::MCtor = 0;
size_t MyString::MAsgn = 0;
size_t MyString::Dtor = 0;

namespace std    //必须放在std内
{
template<>
struct hash<MyString>{    //for unordered containers
    size_t
    operator()(const MyString& s) const noexcept
    { return hash<string>() (string(s.get())); } 
};

(1)定义了Big5和hashfunction,拷贝构造和拷贝赋值的逻辑很重要,具体见代码

(2)move constructor和move assignment operator本质上都是浅拷贝,但在浅拷贝完成以后要把原先对象与资源的联系切断,在本例中表现为将指针置为NULL。所以在析构函数中,释放资源之前要先判断指针是否为NULL

四、使用三中定义的MyString测试容器的效能

测试内容:(1)分别使用定义了move语义的和没有定义move语义的class,对容器进行多次插入操作(都只在尾部插入)。(2)测试拷贝容器,move容器,swap两个容器的效率

结果:(1)vector的两种时间差别巨大,因为vector要扩容,所以涉及很多拷贝操作,使用move会大大节省时间,而对于list,deque,multiset和unordered_multiset两种时间差别不大。但其实按理说deque也可能要搬动元素,但本例中插入的元素都在尾部,所以不涉及搬动操作。(2)所有容器都体现出:copy容器耗时很多,move和swap容器几乎不耗时。这是因为copy容器需要分配空间并依次拷贝元素,但是move容器仅仅是交换了三个指针的值(以vector为例,vector中的三个指针分别是首元素迭代器,尾后迭代器,指示最多容纳元素的迭代器),自然快得不得了。swap应该也只是交换了指针。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值