C++ 右值
什么是左值,右值?一个形象的表述:出现在“=”左边的就是左值,出现在“=”右边的就是右值。形象,但不严谨。
在C++中被广泛认同的:
左值:可以取地址,有名字的;
右值: 不可以取地址,没有名字的。
C++ 11中的右值由两个概念组成:将亡值(expire value),纯右值(pure Rvalue):
将亡值
C++11新增加跟右值引用相关的表达式。这样的表达式通常是将要被移动的对象。例如:返回右值引用T&& 的函数返回值,std::move()的返回值,转换为T&&的类型转换函数的返回值。
纯右值
临时变量和一些跟对象不相关的值。例如:函数返回的非引用的临时变量;一些运算表达式(1+2)产生临时变量值;跟对象不相关的字面值:2, true;此外类型转换函数返回值,lambda表达式也是纯右值。
C++ 容器处理左值右值
C++ 的容器会根据传入的参数类型,进行左值右值的处理。
从效率上面看 emplace(123) > push_back(move(Bjarne(123)) > push_back(Bjarne(123))
测试代码如下:
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <shared_mutex>
#include <string>
#include <memory>
#include <vector>
using namespace std;
class Bjarne
{
public:
Bjarne(int num) :data(new int(num))
{
std::cout << "Construct " << std::endl;
}
Bjarne(const Bjarne &bjarne) :data(new int(*bjarne.data))
{
std::cout << "Copy Construct " << std::endl;
}
Bjarne(Bjarne &&bjarne):data(bjarne.data)
{
bjarne.data = nullptr;
std::cout << "Move Construct " << std::endl;
}
int getValue() {
return *data;
}
~Bjarne() {
delete data;
std::cout << "Destruct " << std::endl;
}
int *data;
};
Bjarne getBjarne(){
return Bjarne(1);
}
int main(int argc, char*argv[])
{
// emplace(1,2,3) > insert(move(AAA(1,2,3)) > insert(AAA(1,2,3))
{
std::cout << "1.0 push_back receive lValue will call Copy Constructor. " << std::endl;
std::cout << " " << std::endl;
std::vector<Bjarne> jackyVec;
Bjarne bjarne(10);
jackyVec.push_back(bjarne);
std::cout << " " << std::endl;
std::cout << " " << std::endl;
}
{
std::cout << " " << std::endl;
std::cout << "1.1 emplace_back receive lValue will call Copy Constructor. " << std::endl;
std::cout << " " << std::endl;
std::vector<Bjarne> jackyVec;
Bjarne bjarne(10);
jackyVec.emplace_back(bjarne);
std::cout << " " << std::endl;
std::cout << " " << std::endl;
}
{
std::cout << " " << std::endl;
std::cout << "1.2 emplace_back receive rValue will call Move Constructor. " << std::endl;
std::cout << " " << std::endl;
std::vector<Bjarne> jackyVec;
Bjarne bjarne(10);
jackyVec.emplace_back(std::move(bjarne));
std::cout << " " << std::endl;
std::cout << " " << std::endl;
}
{
std::cout << " " << std::endl;
std::cout << "1.3 push_back receive rValue will call Move Constructor. " << std::endl;
std::cout << " " << std::endl;
std::vector<Bjarne> jackyVec;
Bjarne bjarne(10);
jackyVec.push_back(std::move(bjarne));
std::cout << " " << std::endl;
std::cout << " " << std::endl;
}
{
std::cout << " " << std::endl;
std::cout << "2.0 Construct object in parameter treated as rValue. push_back" << std::endl;
std::cout << " " << std::endl;
std::vector<Bjarne> jackyVec;
jackyVec.push_back(Bjarne(21));
std::cout << " " << std::endl;
std::cout << " " << std::endl;
}
{
std::cout << "2.1 Construct object in parameter treated as rValue. emplace_back" << std::endl;
std::cout << " " << std::endl;
std::vector<Bjarne> jackyVec;
jackyVec.emplace_back(Bjarne(21));
std::cout << " " << std::endl;
std::cout << " " << std::endl;
}
{
std::cout << " " << std::endl;
std::cout << "3.0 emplace_back in-place call Constructor . " << std::endl;
std::vector<Bjarne> jackyVec;
std::cout << " " << std::endl;
jackyVec.emplace_back(20); //Bjarne(int bjarne)
std::cout << " " << std::endl;
std::cout << " " << std::endl;
}
{
std::cout << " " << std::endl;
std::cout << "4.0 Return value treated as rValue. emplace_back " << std::endl;
std::vector<Bjarne> jackyVec;
std::cout << " " << std::endl;
jackyVec.emplace_back(getBjarne());
std::cout << " " << std::endl;
std::cout << " " << std::endl;
}
{
std::cout << " " << std::endl;
std::cout << "4.1 Return value treated as rValue. push_back " << std::endl;
std::vector<Bjarne> jackyVec;
std::cout << " " << std::endl;
jackyVec.push_back(getBjarne());
std::cout << " " << std::endl;
std::cout << " " << std::endl;
}
return 0;
}
测试结果:
1.0 push_back receive lValue will call Copy Constructor.
Construct
Copy Construct
Destruct
Destruct
1.1 emplace_back receive lValue will call Copy Constructor.
Construct
Copy Construct
Destruct
Destruct
1.2 emplace_back receive rValue will call Move Constructor.
Construct
Move Construct
Destruct
Destruct
1.3 push_back receive rValue will call Move Constructor.
Construct
Move Construct
Destruct
Destruct
2.0 Construct object in parameter treated as rValue. push_back
Construct
Move Construct
Destruct
Destruct
2.1 Construct object in parameter treated as rValue. emplace_back
Construct
Move Construct
Destruct
Destruct
3.0 emplace_back in-place call Constructor .
Construct
Destruct
4.0 Return value treated as rValue. emplace_back
Construct
Move Construct
Destruct
Destruct
4.1 Return value treated as rValue. push_back
Construct
Move Construct
Destruct
Destruct
总结
- emplace() 只有在传入对象的构造参数的时候才会进行“就地构造”。
- emplace() 和push_back()在传入右值的时候,会调用移动构造,效率比拷贝构造更高一些。
- emplace() 和push_back()在传入左值的时候,会调用拷贝构造。
写在最后: 这个实验,纠正了我之前的一些错误理解。