这里首先要区分左值引用、右值引用和左值、右值的关系
int a;
a = 1; // here, a is an lvalue
int x;
int& getRef ()
{
return x;
}
getRef() = 4; //getRef()返回的不是临时变量,是lvalue
其实左值就是指一个拥有地址的表达式。换句话说,左值指向的是一个稳定的内存空间(即可以是在堆上由用户管理的内存空间,也可以是在栈上,离开了一个block就被销毁的内存空间)。上面第二个例子,getRef返回的就是一个全局变量(建立在堆上),所以可以当做左值使用。
int x;
int getVal ()
{
return x;
}
getVal()=4; //error ,getRef()返回的是临时对象,是rvalue,不能做左值
特别注意:&&本身是为了约束等号右边的变量!例如int&& p = a;&&是约束a为右值,而与p无关,p为左值
实际上T&&并不是一定表示右值引用,它的引用类型是未定的,即可能是左值有可能是右值。看看这个例子:
template<typename T>
void f(T&& param);
f(10); //10是右值
int x = 10;
f(x); //x是左值
&&实际上是一个未定的引用类型。这个未定的引用类型被scott meyers称为universal references(可以认为它是种通用的引用类型),它必须被初始化,它是左值应用还是右值引用取决于它的初始化,如果&&被一个左值初始化的话,它就是一个左值引用;如果它被一个右值初始化的话,它就是一个右值引用。&&为universal references时的唯一条件是有类型推断发生,universal references仅仅在T&&下发生,任何一点附加条件都会使之失效,而变成一个右值引用。一般运用于函数模板
template<class T>
void printT(T& a)
{
cout<<"lvalueof&= "<<a<<endl;
}
template<class T>
void printT(T&& a)
{
cout<<"Rvalueof&&= "<<a<<endl;
}
template<class T>
void testForward(T&& a)
{
printT(a);//这里必须注意,a为左值,所以肯定走的printT(T& a)
printT(std::forward<T>(a));
printT(std::move(a));
}
int i = 1;
输入——testForward(i)
lvalueof&= 1
lvalueof&= 1
Rvalueof&&= 1
输入——testForward(22)
lvalueof&= 22
Rvalueof&&= 22
Rvalueof&&= 22
这里即是T&&失效的情况,因为函数模板testforward,以及将T实例为int,所以无需推导,已经确定,所以void fun3(T&& c)中为右值引用,如果int i=1,forwa.fun3(i)会报错,因为i为左值
template<class T>
class testforward
{
public:
void fun1(T&& a)
{
cout<<"test1"<<endl;
}
void fun1(T& b)
{
cout<<"test2"<<endl;
}
void fun3(T&& c)
{
fun1(c);
fun1(std::forward<T>(c));
}
};
输入:testforward<int> forwa;
forwa.fun3(3);
得到:
test2
test1
template<typename T>
void f(std::vector<T>&& param); //同理右值
放在等号左边的变量本身一定是左值,但是其可以是右值引用类型
int&& var1 = x; // var1 is of type int&& (no use of auto here)
auto&& var2 = var1; // var2 is of type int& ,var2的类型是universal references(有类型推导)
1. 这里var1由于是int&& 所以是右值引用,要求等号右边必须为右值,但是其本身是左值
2. 这里var2由于是auto&&,需要结合等号右边推导,var1是左值(int),所以var2位int&型,但是其本身属性为左值!!!
int w1, w2;
auto&& v1 = w1; //w1 is lvalue, v1 is lvalue, type is int&
decltype(w1)&& v2 = w2; //error, w2 is lvalue, v2 is rvalue, type is int&&
用std::move,decltype(w1)&& v2 = std::move(w2); std::move可以将一个左值转换成右值
class MetaData
{
public:
MetaData (int size, const std::string& name)
: _name( name )
, _size( size )
{}
// copy constructor
MetaData (const MetaData& other)
: _name( other._name )
, _size( other._size )
{}
// move constructor
MetaData (MetaData&& other)
: _name( other._name )
, _size( other._size )
{}
std::string getName () const { return _name; }
int getSize () const { return _size; }
private:
std::string _name;
int _size;
};
class ArrayWrapper
{
public:
// default constructor produces a moderately sized array
ArrayWrapper ()
: _p_vals( new int[ 64 ] )
, _metadata( 64, "ArrayWrapper" )
{}
ArrayWrapper (int n)
: _p_vals( new int[ n ] )
, _metadata( n, "ArrayWrapper" )
{}
// move constructor
ArrayWrapper (ArrayWrapper&& other)
: _p_vals( other._p_vals )
, _metadata( other._metadata )
{
other._p_vals = NULL;
}
// copy constructor
ArrayWrapper (const ArrayWrapper& other)
: _p_vals( new int[ other._metadata.getSize() ] )
, _metadata( other._metadata )
{
for ( int i = 0; i < _metadata.getSize(); ++i )
{
_p_vals[ i ] = other._p_vals[ i ];
}
}
~ArrayWrapper ()
{
delete [] _p_vals;
}
private:
int *_p_vals;
MetaData _metadata;
};
注意这里的 == _metadata( other._metadata ) ==,因为 ArrayWrapper (ArrayWrapper&& other) 中,other为左值,所以other._metadata为左值,所以不会走move构造,需要改成std::move(other,_metadata)
ArrayWrapper (ArrayWrapper&& other)
: _p_vals( other._p_vals )
, _metadata( std::move( other._metadata ) )
{
other._p_vals = NULL;
}
右值引用优化性能,避免深拷贝
如果使用std::move(a),则代表a不会再复用,将左值变成右值。变成临时变量后,其他地方将不会再用!生命即将销毁!(这里有误解,表示a不会再用,但是不改变a的生命周期!),即std::move不改变对象的生命周期,只是简单的左值变右值。关于这点可参考如下例子。
#include
#include
struct MoveOnly
{
MoveOnly(int v_)
: v(v_)
{
std::cout << ((void*)this) << " MoveOnly " << v << "\n";
}
~MoveOnly()
{
std::cout << ((void*)this) << " ~MoveOnly " << v << "\n";
}
MoveOnly(const MoveOnly&) = delete;
MoveOnly& operator=(const MoveOnly&) = delete;
MoveOnly(MoveOnly &&src)
{
v = std::exchange(src.v, -1);
std::cout << ((void*)this) << " MoveOnly&& " << v << "\n";
}
MoveOnly& operator=(MoveOnly&&) = default;
MoveOnly&& Apply()
{
std::cout << ((void*)this) << " Apply " << v << "\n";
return std::move(*this);
}
int v;
};
int main()
{
MoveOnly mm = MoveOnly(1);
std::move(mm).Apply();
std::cout<<"?????"<<std::endl;
}
0x7fff65a4274c MoveOnly 1
0x7fff65a4274c Apply 1
?????
0x7fff65a4274c ~MoveOnly 1
针对move,c++11 增加了转移构造函数,传入右值走转移构造,可以节约空间
class DataOnly {
public:
DataOnly () // default constructor
~DataOnly () // destructor
DataOnly (const DataOnly & rhs) // copy constructor
DataOnly & operator=(const DataOnly & rhs) // copy assignment operator
DataOnly (const DataOnly && rhs) // C++11, move constructor
DataOnly & operator=(DataOnly && rhs) // C++11, move assignment operator
};
右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。消除了临时对象的维护 ( 创建和销毁 ) 对性能的影响。
以一个简单的 string 类为示例,实现拷贝构造函数和拷贝赋值操作符。
class MyString {
private:
char* m_data;
size_t m_len;
void copy_data(const char *s) {
m_data = new char[m_len+1];
memcpy(_data, s, m_len);
m_data[_len] = '\0';
}
public:
MyString() {
m_data = NULL;
m_len = 0;
}
MyString(const char* p) {
m_len = strlen (p);
copy_data(p);
}
MyString(const MyString& str) {
m_len = str.m_len;
copy_data(str.m_data);
std::cout << "Copy Constructor is called! source: " << str.m_data << std::endl;
}
MyString& operator=(const MyString& str) {
if (this != &str) {
m_len = str.m_len;
copy_data(str._data);
}
std::cout << "Copy Assignment is called! source: " << str.m_data << std::endl;
return *this;
}
virtual ~MyString() {
if (m_data) free(m_data);
}
};
void test() {
MyString a;
a = MyString("Hello");
std::vector<MyString> vec;
vec.push_back(MyString("World"));
}
实现了调用拷贝构造函数的操作和拷贝赋值操作符的操作。MyString(“Hello”) 和 MyString(“World”) 都是临时对象,也就是右值。虽然它们是临时的,但程序仍然调用了拷贝构造和拷贝赋值,造成了没有意义的资源申请和释放的操作。如果能够直接使用临时对象已经申请的资源,既能节省资源,有能节省资源申请和释放的时间。这正是定义转移语义的目的。
MyString(MyString&& str) {
std::cout << "Move Constructor is called! source: " << str._data << std::endl;
_len = str._len;
_data = str._data; //避免了不必要的拷贝
str._len = 0;
str._data = NULL;
}
MyString& operator=(MyString&& str) {
std::cout << "Move Assignment is called! source: " << str._data << std::endl;
if (this != &str) {
_len = str._len;
_data = str._data; //避免了不必要的拷贝
str._len = 0;
str._data = NULL;
}
return *this;
}
析构函数加判断是否为NULL,是就不用再delete!
muduo代码实例
T take()
{
MutexLockGuard lock(mutex_);
// always use a while-loop, due to spurious wakeup
while (queue_.empty())
{
notEmpty_.wait();
}
assert(!queue_.empty());
T front(std::move(queue_.front())); //这里用了move,因为queue_.front()马上要被pop掉,所以使用move,节省资源开销
queue_.pop_front();
return front;
}