二十分钟弄懂C++11 的 rvalue reference (C++ 性能剖析 (5))
C++ 11加了许多新的功能。其中对C++性能和我们设计class的constructor或assignment可能产生重大影响的非rvalue reference莫属!我看了不少资料,能说清它的不多。下面我企图用简单的例子来说明,希望读者能够理解并应用这一重要的语言构造。
1.rvalue reference 是reference (即指针)
比如下面两条语句的语义完全一样:
int &&p = 3; // line 1
const int &cp = 3 // line 2
2. rvalue reference 指向临时变量
上面的line1和line2的共同点是,他们都指向临时变量。所不同的是下面两句:
p = 5; // p 的内容变成了5
cp = 5; // 编译出错:cp 不能改动(常数)
3.rvalue reference可以简化moving 语义 – 提高object 拷贝性能
很好,我们现在可以通过rvalue reference修改(阴暗中的)临时变量了。那么这有什么用呢?目前C++11所宣称的最主要的应用就是所谓的“moving semantics (迁移语义)”。请看下面例子:
class SimpleString
{
char * _ptr;
public:
SimpleString(const char *p);
SimpleString(const SimpleString & another);
~SimpleString();
operator const char * () { return _ptr; }
SimpleString & operator = (const SimpleString & another);
static void Test();
private:
void GetStr(const char *p);
};
SimpleString::SimpleString(const char *p): _ptr(nullptr)
{
GetStr(p);
}
SimpleString::SimpleString(const SimpleString & another): _ptr(nullptr)
{
GetStr(another._ptr);
}
void SimpleString::GetStr(const char *p)
{
if (_ptr)
delete [] _ptr;
size_t l= ::strlen(p);
_ptr = new char[l+1];
::strcpy_s(this->_ptr, l+1, p);
}
SimpleString::~SimpleString()
{
if (_ptr)
{
delete [] _ptr;
printf("SimpleString d'tr called for \n");
}
}
SimpleString & SimpleString::operator = ( const SimpleString & another)
{
GetStr(another._ptr);
return *this;
}
namespace
{
// simple string factory
SimpleString CreateString()
{
SimpleString temp("A temp string created!");
return temp;
}
}
void SimpleString::Test()
{
SimpleString ret = CreateString();
printf("ret is: &s \n", ret);
}
上面是一个为了试验用的简单string class。 假设我们有一个函数CreateString, 返回一个创建的SimpleString 值。然后赋给接受变量ret。 这个简单的逻辑有什么问题呢?
这里就是临时变量和copy constructor的问题。我们这里用了SimpleString::SimpleString(const SimpleString & another),它用GetStr来构建一个新的指针_ptr。然后将临时变量的_ptr所指内容拷贝过来。
这是常见的做法,但是很昂贵的。CreateString函数已经构建了一个有效的_ptr,为什不能拷贝指针呢?
原来,因为CreateString里的temp变量是临时变量,它在CreateString出口时将会被销毁,除非我们能获取他的reference或pointer,然后将它的_ptr设为null。这是个好主意,我们再加一个函数:
void SimpleString::MoveStr(SimpleString & another)
{
if (this->_ptr)
delete this->_ptr;
this->_ptr = another._ptr;
another._ptr = nullptr;
}
然后把copy constructor 改写:
SimpleString::SimpleString(const SimpleString & another): _ptr(nullptr)
{
MoveStr(const_cast<SimpleString &>(another)); // line 100
}
这样一来,我们就只构建一次_ptr了,测试的结果也证明了这一点。
上面讲的和rvalue reference有何关系呢?
我对line 100的方案不太满意:
1) 我们改变了原来copy constructor的常规意义,现在只要你赋值与另一变量,你就失去了你自己的值。我们希望这个功能只适合于“临时变量”。
2) const_cast 不太好,不美观。
现在,我们因该悟出rvalue reference的意义了吧?
根据第二节,rvalue reference是指向临时变量的,正好是用于指向CreateString产生的临时变量。
原来,只要我们在SimpleString里加一个moving copy constructor(注意&&):
SimpleString::SimpleString(SimpleString && another): _ptr(nullptr)
{
MoveStr(another);
}
我们便无需更改SimpleString::SimpleString(const SimpleString & another)了。C++编译自动地在这一行SimpleString ret = CreateString() call 我们的moving constructor SimpleString::SimpleString(SimpleString && another), 而不是我们的copy constructor.
大家不妨试试!
总结
C++11利用rvalue reference,使我们可以方便地实现 moving constructor 语义。这对上述类似的问题(特别是std里的container用法)提供了解决C++传统的临时变量拷贝的功能隐患。
附录:修改后的代码
// header: RValueRef.h
class SimpleString
{
char * _ptr;
public:
SimpleString(const char *p);
SimpleString(const SimpleString & another);
SimpleString(SimpleString && another); // moving constructor
~SimpleString();
operator const char * () { return _ptr; }
SimpleString & operator = (const SimpleString & another);
SimpleString & SimpleString::operator = ( SimpleString && another);
static void Test();
private:
void GetStr(const char *p);
void MoveStr(SimpleString & another);
};
// C++: rvalue.cpp
#include "stdafx.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include "RValueRef.h"
SimpleString::SimpleString(const char *p): _ptr(nullptr)
{
GetStr(p);
}
SimpleString::SimpleString(const SimpleString & another): _ptr(nullptr)
{
GetStr(another._ptr);
}
// Moving constructor helps move temp var’s _ptr to ourselves.
SimpleString::SimpleString(SimpleString && another): _ptr(nullptr)
{
MoveStr(another);
}
void SimpleString::GetStr(const char *p)
{
if (_ptr)
delete [] _ptr;
size_t l= ::strlen(p);
_ptr = new char[l+1];
::strcpy_s(this->_ptr, l+1, p);
}
SimpleString::~SimpleString()
{
if (_ptr)
{
printf("SimpleString d'tr called for '%s'\n", _ptr);
delete [] _ptr;
}
}
SimpleString & SimpleString::operator = ( const SimpleString & another)
{
GetStr(another._ptr);
return *this;
}
SimpleString & SimpleString::operator = ( SimpleString && another)
{
MoveStr(another);
return *this;
}
void SimpleString::MoveStr(SimpleString & another)
{
if (this->_ptr)
delete this->_ptr;
this->_ptr = another._ptr;
another._ptr = nullptr; // don’t forget to do this
}
namespace
{
SimpleString CreateString()
{
SimpleString temp("A temp string created!");
return temp;
}
}
void SimpleString::Test()
{
SimpleString ret = CreateString();
printf("ret is: &s \n", ret);
}