C++11加入了右值引用(value reference)的概念(用&&标识),用来区分对左值和右值的引用。左值就是一个有名字的对象,而右值则是一个无名对象(临时对象)。move语义允许修改右值(以前右值被看作是不可修改的,等同于const T&类型)。c++11中所有的值必须是左值、右值、将亡值之一。
即:在使用右值引用的时候,因为右值是一个无名的临时对象,对于对象的移动创建和移动复制操作,就可以不用重新申请内存空间,直接利用这个临时对象的内存空间和空间内的值,完成上述的操作。节约的新对象的内存分配,内存拷贝,和临时对象的内存释放。
一个有名字的对象就是左值。为了再把它变为右值引用(以便调用move constructor)必须使用std::move。标准库
标准库
用一个std::move_if_noexcept模板函数替代move函数。可以在类的移动狗制造函数没有noexcept关键字修饰时返回一个左值引用从而使变量可以使用拷贝语义,而在类的移动构造函数有noexcept关键字时,返回一个右值引用,从而是变量可以使用移动语义。
完美转发:在函数模板中,完全依照模板的参数类型,将参数传递给函数模板中调用的另一个函数
template
viod IamForwording(T t){ IrunCodeActually (t); }
注意这种情况,会增加一次参数的拷贝,并且如果转发函数只能接收有一部分类型,那对执行函数的参数就会造成限制。
c++11中引用折叠:将复杂的位置表达式折叠为已知的简单表达式。如下表:
std::forward可以保存参数的左值或右值特性。
template
void IamForwording(T t) { IrinCodeActually(forword(t)); }
C++的class或者struct以前都有一些隐含的成员函数:默认构造函数(仅当没有显示定义任何其他构造函数时才存在),拷贝构造函数,析构函数还有拷贝赋值操作符。拷贝构造函数和拷贝赋值操作符提供bit-wise的拷贝(浅拷贝),也就是逐个bit拷贝对象。也就是说,如果你有一个类包含指向其他对象的指针,拷贝时只会拷贝指针的值而不会管指向的对象。在某些情况下这种做法是没问题的,但在很多情况下,实际上你需要的是深拷贝,也就是说你希望拷贝指针所指向的对象。而不是拷贝指针的值。这种情况下,你需要显示地提供拷贝构造函数与拷贝赋值操作符来进行深拷贝。如果你用来初始化或拷贝的源对象是个右值(临时对象)会怎么样呢?你仍然需要拷贝它的值,但随后很快右值就会被释放。这意味着产生了额外的操作开销,包括原本并不需要的空间分配以及内存拷贝。现在说说move constructor和move assignment operator。这两个函数接收T&&类型的参数,也就是一个右值。在这种情况下,它们可以修改右值对象,例如“偷走”它们内部指针所指向的对象。举个例子,一个容器的实现(例如vector或者queue)可能包含一个指向元素数组的指针。当用一个临时对象初始化一个对象时,我们不需要分配另一个数组,从临时对象中把值复制过来,然后在临时对象析构时释放它的内存。我们只需要将指向数组内存的指针值复制过来,由此节约了一次内存分配,一次元数组的复制以及后来的内存释放。以下代码实现了一个简易的buffer。这个buffer有一个成员记录buffer名称(为了便于以下的说明),一个指针(封装在unique_ptr中)指向元素为T类型的数组,还有一个记录数组长度的变量。
template <typename T>
class Buffer
{
std::string _name;
size_t _size;
std::unique_ptr
public:
// default constructor
Buffer():
_size(16),
_buffer(new T[16])
{}
// constructor
Buffer(const std::string& name, size_t size):
_name(name),
_size(size),
_buffer(new T[size])
{}
// copy constructor
Buffer(const Buffer& copy):
_name(copy._name),
_size(copy._size),
_buffer(new T[copy._size])
{
T* source = copy._buffer.get();
T* dest = _buffer.get();
std::copy(source, source + copy._size, dest);
}
// copy assignment operator
Buffer& operator=(const Buffer& copy)
{
if(this != ©)
{
_name = copy._name;
if(_size != copy._size)
{
_buffer = nullptr;
_size = copy._size;
_buffer = _size > 0 > new T[_size] : nullptr;
}
T* source = copy._buffer.get();
T* dest = _buffer.get();
std::copy(source, source + copy._size, dest);
}
return *this;
}
// move constructor
Buffer(Buffer&& temp):
_name(std::move(temp._name)),
_size(temp._size),
_buffer(std::move(temp._buffer))
{
temp._buffer = nullptr;
temp._size = 0;
}
// move assignment operator
Buffer& operator=(Buffer&& temp)
{
assert(this != &temp); // assert if this is not a temporary
_buffer = nullptr;
_size = temp._size;
_buffer = std::move(temp._buffer);
_name = std::move(temp._name);
temp._buffer = nullptr;
temp._size = 0;
return *this;
}
};
template <typename T>
Buffer
{
Buffer
return b;
}
int main(){
Buffer<int> b1;
Buffer<int> b2("buf2", 64);
Buffer<int> b3 = b2;
Buffer<int> b4 = getBuffer<int>("buf4");
b1 = getBuffer<int>("buf5");
return 0;
}
默认的copy constructor以及copy assignment operator大家应该很熟悉了。C++11中新增的是move constructor以及move assignment operator,这两个函数根据上文所描述的move语义实现。如果你运行这段代码,你就会发现b4构造时,move constructor会被调用。同样,对b1赋值时,move assignment operator会被调用。原因就在于getBuffer()的返回值是一个临时对象——也就是右值。你也许注意到了,move constuctor中当我们初始化变量name和指向buffer的指针时,我们使用了std::move。name实际上是一个string,std::string实现了move语义。std::unique_ptr也一样。但是如果我们写_name(temp._name),那么copy constructor将会被调用。不过对于_buffer来说不能这么写,因为std::unique_ptr没有copy constructor。但为什么std::string的move constructor此时没有被调到呢?这是因为虽然我们使用一个右值调用了Buffer的move constructor,但在这个构造函数内,它实际上是个左值。为什么?因为它是有名字的——“temp”。一个有名字的对象就是左值。为了再把它变为右值(以便调用move constructor)必须使用std::move。这个函数仅仅是把一个左值引用变为一个右值引用。更新:虽然这个例子是为了说明如何实现move constructor以及move assignment operator,但具体的实现方式并不是唯一的。某同学提供了另一种可能的实现。为了方便查看,我把它也列在下面:
template <typename T>
class Buffer
{
std::string _name;
size_t _size;
std::unique_ptr
public:
// constructor
Buffer(const std::string& name = "", size_t size = 16):
_name(name),
_size(size),
_buffer(size? new T[size] : nullptr)
{}
// copy constructor
Buffer(const Buffer& copy):
_name(copy._name),
_size(copy._size),
_buffer(copy._size? new T[copy._size] : nullptr)
{
T* source = copy._buffer.get();
T* dest = _buffer.get();
std::copy(source, source + copy._size, dest);
}
// copy assignment operator
Buffer& operator=(Buffer copy)
{
swap(*this, copy);
return *this;
}
// move constructor
Buffer(Buffer&& temp):Buffer()
{
swap(*this, temp);
}
friend void swap(Buffer& first, Buffer& second) noexcept
{
using std::swap;
swap(first._name , second._name);
swap(first._size , second._size);
swap(first._buffer, second._buffer);
}
};
如果一个表达式是函数调用或重载运算符表达式,且其返回类型为右值引用,例如 std::move(x),那么这个表达式是xvalue表达式
首先,如果要返回一个对象,肯定是要在栈上面预留内存空间的,所以这个对象是有身份的。第二,返回类型是右值引用,所以它会让使用这个表达式的语句呈现移动语义,所以是可移动的
#include
void f(int& i) { std::cout << "lvalue ref: " << i << "\n"; }
void f(int&& i) { std::cout << "rvalue ref: " << i << "\n"; }
int main()
{
int i = 77;
f(i); // lvalue ref called
f(99); // rvalue ref called
f(std::move(i)); // 稍后介绍
return 0;
}
实际上,右值引用主要用于创建移动构造函数和移动赋值运算。
移动构造函数类似于拷贝构造函数,把类的实例对象作为参数,并创建一个新的实例对象。
但是 移动构造函数可以避免内存的重新分配,因为我们知道右值引用提供了一个暂时的对象(类似于存在对上的临时对象),而不是进行copy,所以我们可以进行移动。 我们不想拷贝将要消失的临时对象,所以这个临时对象的资源可以被我们用作于其他的对象。
#include
#include
class A
{
public:
// Simple constructor that initializes the resource.
explicit A(size_t length)
: mLength(length), mData(new int[length])
{
std::cout << "A(size_t). length = "
<< mLength << "." << std::endl;
}
// Destructor.
~A()
{
std::cout << "~A(). length = " << mLength << ".";
if (mData != NULL) {
std::cout << " Deleting resource.";
delete[] mData; // Delete the resource.
}
std::cout << std::endl;
}
// Copy constructor.
A(const A& other)
: mLength(other.mLength), mData(new int[other.mLength])
{
std::cout << "A(const A&). length = "
<< other.mLength << ". Copying resource." << std::endl;
std::copy(other.mData, other.mData + mLength, mData);
}
// Copy assignment operator.
A& operator=(const A& other)
{
std::cout << "operator=(const A&). length = "
<< other.mLength << ". Copying resource." << std::endl;
if (this != &other) {
delete[] mData; // Free the existing resource.
mLength = other.mLength;
mData = new int[mLength];
std::copy(other.mData, other.mData + mLength, mData);
}
return *this;
}
// Move constructor.
A(A&& other) : mData(NULL), mLength(0)
{
std::cout << "A(A&&). length = "
<< other.mLength << ". Moving resource.\n";
// Copy the data pointer and its length from the
// source object.
mData = other.mData;
mLength = other.mLength;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other.mData = NULL;
other.mLength = 0;
}
// Move assignment operator.
A& operator=(A&& other)
{
std::cout << "operator=(A&&). length = "
<< other.mLength << "." << std::endl;
if (this != &other) {
// Free the existing resource.
delete[] mData;
// Copy the data pointer and its length from the
// source object.
mData = other.mData;
mLength = other.mLength;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other.mData = NULL;
other.mLength = 0;
}
return *this;
}
// Retrieves the length of the data resource.
size_t Length() const
{
return mLength;
}
private:
size_t mLength; // The length of the resource.
int* mData; // The resource.
};
移动构造函数
语法:
A(A&& other) noexcept // C++11 - specifying non-exception throwing functions
{
mData = other.mData; // shallow copy or referential copy
other.mData = nullptr;
}
最主要的是没有用到新的资源,是移动而不是拷贝。
假设一个地址指向了一个有一百万个int元素的数组,使用move构造函数,我们没有创造什么,所以代价很低。
// Move constructor.
A(A&& other) : mData(NULL), mLength(0)
{
// Copy the data pointer and its length from the
// source object.
mData = other.mData;
mLength = other.mLength;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other.mData = NULL;
other.mLength = 0;
}
移动赋值运算符
语法:
A& operator=(A&& other) noexcept
{
mData = other.mData;
other.mData = nullptr;
return *this;
}
工作流程这样的:Google上这么说的:
Release any resources that *this currently owns.
Pilfer other’s resource.
Set other to a default state.
Return *this.
// Move assignment operator.
A& operator=(A&& other)
{
std::cout << "operator=(A&&). length = "
<< other.mLength << "." << std::endl;
if (this != &other) {
// Free the existing resource.
delete[] mData;
// Copy the data pointer and its length from the
// source object.
mData = other.mData;
mLength = other.mLength;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other.mData = NULL;
other.mLength = 0;
}
return *this;
}
让我们看几个move带来的好处吧!
vector众所周知,C++11后对vector也进行了一些优化。例如vector::push_back()被定义为了两种版本的重载,一个是cosnt T&左值作为参数,一个是T&&右值作为参数。例如下面的代码:
std::vector v;
v.push_back(A(25));
v.push_back(A(75));
上面两个push_back()都会调用push_back(T&&)版本,因为他们的参数为右值。这样提高了效率。
而 当参数为左值的时候,会调用push_back(const T&) 。
#include
int main()
{
std::vector v;
A aObj(25); // lvalue
v.push_back(aObj); // push_back(const T&)
}
但事实我们可以使用 static_cast进行强制:
// calls push_back(T&&)
v.push_back(static_cast(aObj));
我们可以使用std::move完成上面的任务:
v.push_back(std::move(aObj)); //calls push_back(T&&)
似乎push_back(T&&)永远是最佳选择,但是一定要记住:
push_back(T&&) 使得参数为空。如果我们想要保留参数的值,我们这个时候需要使用拷贝,而不是移动。
最后写一个例子,看看如何使用move来交换两个对象:
#include
using namespace std;
class A
{
public:
// constructor
explicit A(size_t length)
: mLength(length), mData(new int[length]) {}
// move constructor
A(A&& other)
{
mData = other.mData;
mLength = other.mLength;
other.mData = nullptr;
other.mLength = 0;
}
// move assignment
A& operator=(A&& other) noexcept
{
mData = other.mData;
mLength = other.mLength;
other.mData = nullptr;
other.mLength = 0;
return *this;
}
size_t getLength() { return mLength; }
void swap(A& other)
{
A temp = move(other);
other = move(*this);
*this = move(temp);
}
int* get_mData() { return mData; }
private:
int *mData;
size_t mLength;
};
int main()
{
A a(11), b(22);
cout << a.getLength() << ' ' << b.getLength() << endl;
cout << a.get_mData() << ' ' << b.get_mData() << endl;
swap(a,b);
cout << a.getLength() << ' ' << b.getLength() << endl;
cout << a.get_mData() << ' ' << b.get_mData() << endl;
return 0;
}