右值(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类,对容器进行多次插入操作和拷贝赋值操作
- vector两种时间差别巨大,因为vector要扩容,涉及拷贝操作,使用move会节省很多时间
- 其他容器的两种时间差别不大,其中deque若插入的元素在中间时也会产生大量元素拷贝
- 对所有容器而言,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
在这里插入图片描述
调用: