今天看到了两个名词move语意和完美转发,据说move semantics 能带来巨大的性能提升,而 perfect forwarding 让高度泛型代码的编写变得非常容易。我对此很感兴趣。这里进行一个初步研究。
首先区分lvaules和rvalues.
1. 他们都是针对表达式的,不是针对于某个变量。
2. 基本理解可以使左值和右值,那我们就可以想象下,这样的式子(lvaules = rvalues),左值是持久的,而右值是临时的。(这里我猜测是不是move semantics就是减少临时变量而增加的性能)
3. 他们可以用&去区分,取址操作要求它的“操作数必须是一个 lvalue”,临时的值取地址是很危险的
看几个列子
Lvaules:
obj,*ptr, prt[index], ++x
Rvalues:
1729 , x + y , std::string("meow"), x++
这里注意下:++x和x++ ,(可以用y=x++和y=++x考虑下,当x初始化为1. 第一个y=1,第二个是2).
lvalue 和 rvalue 两者都有非常量(modifiable,也就是说non-const)与常量(const )之分。举例来说:
string one("cute"); // modifiablelvalue
const string two("fluffy"); / / const lvalue
string three() { return "kittens"; } // modifiable rvalue
const string four() { return "are an essential part of ahealthy diet"; }// const rvalue
Type& 只可绑定到非常量lvalue ,
const Type& 可以绑定到: 非常量 lvalues, const lvalues,非常量 rvalues 以及 const values。
举例:
void mutate(string& ref)。
mutate(one) right
mutate(two) wrong
mutate(three()) wrong
mutate(four()) wrong
mutate("purr") wrong
classTClass {
public:
TClass(TClass&a) ///à这里应该需要TClass(const TClass& a)
{
}
int x;
};
TClass& foo(void) { ///->这里应该是TClass foo(void)
returnTClass(); //这里是Rvalues,返回引用是错误的。需要返回值然后调用拷贝构造
}
TClass1 p =foo();
通常,如果能够检测到非常量 rvalue,你就能够做些“资源窃取”的优化。如果非常量 rvalue 所引用的那些对象持有任何资源(如内存),你就能窃取它们的资源而不用拷贝它们,反正它们很快就会被销毁掉。通过窃取非常量 rvalue 持有的资源来构建或赋值的手法通常被称作 “moving”,可移动对象拥有 “move 语意”
C++0x 引进了一种新的引用,rvalue 引用,其语法是 Type&& 和 const Type&& .目前 C++0x 草案 N2798 8.3.2/2 上说:“用 & 声明的引用类型被称作 lvalue 引用,而用 && 声明的引用类型被称作 rvalue 引用
两者有什么区别?与 lvalue 引用相比, rvalue 引用在初始化与重载决议时表现出不同的行为。两者的区别在于它们会优先绑定到什么东西上(初始化时)和什么东西会优先绑定到它们身上(重载决议时)
非常量 lvalue 引用( Type& ) 只能绑定到非常量 lvalue 上,而其他的一概不能(如 const lvalues,非常量 rvalues,const rvalues)
const lvalue 引用( const Type& )能绑定到任何东西上。
非常量 rvalue ( Type&& )能够绑定到非常量 lvalue 以及非常量 rvalue 上,而不能绑定到 const lvalues 和 const rvalues (这会违背 const 正确性)
const rvalue 引用( constType&& ) 能够绑定到任何东西上.
来源于两条简单的规则:
· 遵守 const 正确性,所以你不能把非常量引用绑定到常量上。
· 避免意外修改临时对象,所以你不能把非常量 lvalue 引用绑定到非常量 rvalue 上来.
右值引用支持移动语义的实现,这可以显著提高应用程序的性能。利用移动语义,您可以编写将资源(如动态分配的内存)从一个对象转移到另一个对象的代码。移动语义很有效,因为它使资源能够从无法在程序中的其他位置引用的临时对象转移。
若要实现移动语义,您通常可以向您的类提供移动构造函数,也可以提供移动赋值运算符 (operator=)。其源是右值的复制和赋值操作随后会自动利用移动语义。与默认复制构造函数不同,编译器不提供默认移动构造函数。有关如何编写移动构造函数以及如何在应用程序中使用它的详细信息,请参阅移动构造函数和移动赋值运算符 (C++)。
为更好学习,引用了网上的列子
#include
#include
#include
using namespace std;
class MemoryBlock
{
public:
// Simple constructor that initializes the resource.
explicit MemoryBlock(size_t length)
: _length(length)
, _data(new int[length])
{
std::cout << "In MemoryBlock(size_t). length = "
<< _length << "." << std::endl;
}
// Destructor.
~MemoryBlock()
{
std::cout << "In ~MemoryBlock(). length = "
<< _length << ".";
if (_data != nullptr)
{
std::cout << " Deleting resource.";
// Delete the resource.
delete[] _data;
}
std::cout << std::endl;
}
// Copy constructor.
MemoryBlock(const MemoryBlock& other)
: _length(other._length)
, _data(new int[other._length])
{
std::cout << "In MemoryBlock(const MemoryBlock&). length = "
<< other._length << ". Copying resource." << std::endl;
std::copy(other._data, other._data + _length, _data);
}
// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other)
{
std::cout << "In operator=(const MemoryBlock&). length = "
<< other._length << ". Copying resource." << std::endl;
if (this != &other)
{
// Free the existing resource.
delete[] _data;
_length = other._length;
_data = new int[_length];
std::copy(other._data, other._data + _length, _data);
}
return *this;
}
#if 1
// Move constructor.
MemoryBlock(MemoryBlock&& other)
: _data(nullptr)
, _length(0)
{
std::cout << "In MemoryBlock(MemoryBlock&&). length = "
<< other._length << ". Moving resource." << std::endl;
// Copy the data pointer and its length from the
// source object.
_data = other._data;
_length = other._length;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other._data = nullptr;
other._length = 0;
}
#else
// Move constructor.
MemoryBlock(MemoryBlock&& other)
: _data(nullptr)
, _length(0)
{
*this = std::move(other);
}
#endif
// Move assignment operator.
MemoryBlock& operator=(MemoryBlock&& other)
{
std::cout << "In operator=(MemoryBlock&&). length = "
<< other._length << "." << std::endl;
if (this != &other)
{
// Free the existing resource.
delete[] _data;
// Copy the data pointer and its length from the
// source object.
_data = other._data;
_length = other._length;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other._data = nullptr;
other._length = 0;
}
return *this;
}
// Retrieves the length of the data resource.
size_t Length() const
{
return _length;
}
private:
size_t _length; // The length of the resource.
int* _data; // The resource.
};
int main()
{
// Create a vector object and add a few elements to it.
vector
v;
v.push_back(MemoryBlock(25));
v.push_back(MemoryBlock(75));
// Insert a new element into the second position of the vector.
v.insert(v.begin() + 1, MemoryBlock(50));
}
运行结果如下:
In MemoryBlock(size_t). length = 25. In MemoryBlock(MemoryBlock&&). length = 25. Moving resource. In ~MemoryBlock(). length = 0. In MemoryBlock(size_t). length = 75. In MemoryBlock(MemoryBlock&&). length = 25. Moving resource. In ~MemoryBlock(). length = 0. In MemoryBlock(MemoryBlock&&). length = 75. Moving resource. In ~MemoryBlock(). length = 0. In MemoryBlock(size_t). length = 50. In MemoryBlock(MemoryBlock&&). length = 50. Moving resource. In MemoryBlock(MemoryBlock&&). length = 50. Moving resource. In operator=(MemoryBlock&&). length = 75. In operator=(MemoryBlock&&). length = 50. In ~MemoryBlock(). length = 0. In ~MemoryBlock(). length = 0. In ~MemoryBlock(). length = 25. Deleting resource. In ~MemoryBlock(). length = 50. Deleting resource. In ~MemoryBlock(). length = 75. Deleting resource.
完美转发
完美转发可减少对重载函数的需求,并有助于避免转发问题。当您编写采用引用作为其参数的泛型函数,并且该函数将这些参数传递(或转发)给另一个函数时,将会引发转发问题。例如,如果泛型函数采用const T& 类型的参数,则调用的函数无法修改该参数的值。如果泛型函数采用T& 类型的参数,则无法使用右值(如临时对象或整数文本)来调用该函数。
通常,若要解决此问题,则必须提供为其每个参数采用 T& 和 const T& 的重载版本的泛型函数。因此,重载函数的数量将基于参数的数量呈指数方式增加。利用右值引用,您可以编写一个版本的函数,该函数可接受任意参数并将其转发给另一个函数,就像已直接调用其他函数一样.
为更好学习,引用了网上的列子