赞
踩
这里首先要区分左值引用、右值引用和左值、右值的关系
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 <future> #include <iostream> 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;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。