C++11新特性(三 标准库)

右值(rvalue)引用

右值的类型:临时对象

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

其中a+b为右值,不能通过编译。但有一些class的临时对象可以被赋值

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

可以通过编译,但赋值无意义

为什么要引入“右值引用”

当rvalue出现在operator=(copy assignment)右侧,我们认为右边的资源是可以被偷取的,比如说将临时对象赋值给一个变量,即让该变量偷取了临时对象的资源。而要实现这个过程,需要:
(1)有语法在调用端告诉编译器:这是个rvalue
(2)有语法让我们在被调用端写出一个专门处理rvalue的move assignment函数
例子在这里插入图片描述
(1)insert()往容器中插入元素,涉及到元素的移动,为了效率insert()需要设置右值引用的版本,即insert(…&&x),用来调用临时对象x的ctor。(注意调用的ctor为对象里设置的move ctor版本)
(2)对象的move ctor逻辑:简单copy指针的值,使copy的指针和原对象指向同一个资源,同时将原来的对象对资源的引用销毁,保证原来的对象不再使用,这样才算安全。
(3)std::move()可以对一个左值进行右值引用调用,在调用结束后该左值不再使用

perfect forwarding和move

move

move的源码如下

template<typename _Tp>
    constexpr typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

其调用了一个std::remove_reference< _Tp>::type提取了_Tp中的底层类型
std::remove_reference源码如下
在这里插入图片描述
可以看到,std::remove_reference 结构体的实现非常简单,功能就是依靠模板把传参 _Tp 的类型分离出来,当调用 std::remove_reference::type 时即为分离出的最底层类型。即 int& 和 int&& 都可以提取出基础类型 int。
static_cast分析
其返回值为 “新类型” 类型的值,例如 n = static_cast(3.14) 后,此时 n = 3。我个人认为可以粗略的将 static_cast 当作一个更高级的强制类型转换,相比传统的强制类型转换,static_cast 会对转换类型进行检测,所以相对更加安全。可以说,任何具有明确定义的类型转换,只要不包含底层const,都可以使用 static_cast。
std::move实例
由上文可知两个关键内容的作用,则可首先带入一个实例来化简分析 std::move 的实际作用。

首先是一小部分代码:

std::string s = "Hello";
std::vector<std::string> v;
v.push_back(std::move(s));

根据上面std::move的流程,std::move(s)的执行return语句为:

string&& move(string& && t) //此处string& &&等于string&,下文会提及
{
1. return static_cast<typename std::remove_reference<_Tp>::type&&>(__t);
2. return static_cast<typename std::remove_reference<string&>::type&&>(s);//提取出基础类型string
3. return static_cast<string&&>(s)
}

std::move中的引用折叠
通过上文的内容,可以发现 std::move 中的传参类型为 _Tp&& ,那么执行过程中,传参s的类型是什么呢?

std::string s = "Hello";
std::move(s) => std::move(string& &&)

那么string& &&是什么呢,这里涉及到了引用折叠的概念
简单来说就是除了右值的&&是右值,其他都是左值。

  • X& &、X&& &、X& && —— 折叠成X&,用于处理左值
  • X&& && —— 折叠成X&&,用于处理右值

在标准库中实现了许多的针对move版本的和copy版本的动作,如iterator中的insert

iterator
insert(const_iterator __position, const value_type& __x);

iterator
insert(const_iterator __position, value_type&& _x)
{return emplace(__position, std::move(__x));}

emplace使用:
当调用push或insert成员函数时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中。而当调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数。emplace成员使用这些参数在容器管理的内存空间中直接构造元素。换句话说,传统操作会先创建一个临时对象,再把临时对象的值赋给目标对象的内存空间,在类类型中这会触发两次构造函数(一次是初始化构造,一次是拷贝构造)。而emplace操作则直接在目标对象的内存空间中扩展,构造新的对象,省去了中间步骤。这样在时间上和空间上的效率都得到了提高。这一点在插入内存占用较大的复合类型中体现得尤为明显。

move在传递中的不完美

在这里插入图片描述

forward

std::forward 实现完美转发,作用就是保持传参参数属性不变,如果原来的值是左值,经std::forward处理后该值还是左值;如果原来的值是右值,经std::forward处理后它还是右值。

//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); 
    }

调用方式:

void process(int& x)
{
	cout << "process(int&)" << '\t' << x << endl;
	cout << endl;
}
//右值引用
void process(int&& x)
{
	cout << "process(int&&)" << '\t' << x << endl;
	cout << endl;
}
//不完美转发
void Forward1(int&& x)
{
	cout << "Forward1(int&& x)" << endl;
	process(x);		//不是完美转发,x被当做左值,调用process(int& x)
}
//完美转发
void Forward2(int&& x)
{
	cout << "Forward2(int&& x)" << endl;
	process(forward<int>(x));    //完美转发,x被当做右值
}

对比move

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())); } 
}
  • 定义了Big 5和hash function重载操作符
  • move constructor和move assignment都是浅拷贝,但在浅拷贝完成以后要把原先对象与资源切断,在这里为将指针置为NULL。因此在析构函数中,释放资源前要先判断指针是否为NULL。

使用MyString测试容器性能

分别使用了定义move语义和没有定义move语义的MyString类,对容器进行多次插入操作和拷贝赋值操作

  1. vector两种时间差别巨大,因为vector要扩容,涉及拷贝操作,使用move会节省很多时间
  2. 其他容器的两种时间差别不大,其中deque若插入的元素在中间时也会产生大量元素拷贝
  3. 对所有容器而言,copy耗时很多,move和swap几乎不耗时。原因是copy容器需要分配新的空间并依次拷贝元素,move容器仅仅是交换了三个指针的值(以vector为例,vector三个指针的值分别是首元素迭代器,尾后迭代器,指示,指示最多容纳元素的迭代器),而swap也是交换指针,时间都很快。

新增容器

Array,Forward-List,Unordered Container

TR1版array

template<typename _Tp, std::size_t _Nm>
struct array
{
    typedef _Tp            value_type;
    typedef _Tp*           pointer;
    typedef value_type*    iterator;
 
    value_type _M_instance[_Nm ? _Nm : 1];
    
    iterator begin()
    { return iterator(&_M_instance[0]); }
 
    iterator end()
    { return iterator(&_M_instance[_Nm]); }
 
    ......
};

注意里面没有构造函数,完整的源码查看C++11新特性 二 keywords

容器hashtable

原理:用链地址法解决冲突的哈希表,key的计算方式是对哈希表长度取余,且最后哈希表长度为一个素数。如果哈希表长度放不下了,就要进行refreshing,将哈希表扩容为大概原来的两倍,扩容后长度依然是素数。
存放时要告诉hashtable计算对象的hashfunction

hashfunction

利用Varadic templates递归计算

class CustomerHash{
public:
    std::size_t operator() (const Customer& c) const {
        return hash_val(c.fname, c.lname, c.no);
    }
};
 
template <typename T, typename... Types>
inline void hash_val(size_t& seed, const T& val, const Types&... args){
    hash_combine(seed, val);
    hash_val(seed, args);
}
 
template <typename... Types>
inline size_t hash_val(const Types&... args){
    size_t seed = 0;
    hash_val(seed, args...);
    return seed;
}
 
template <typename T>
inline void hash_val(size_t& seed, const T& val){
    hash_conbine(seed, val);
}

tuple

在这里插入图片描述
在这里插入图片描述
调用:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值