C++11右值引用实际使用示例
一、概念,什么是左值和右值
左值:
int x = 8;
int & func()
{
return x; //不能将临时变量以左值返回,这里返回个全局变量的引用
}
func() = 10; //func()返回的是左值,可以放在左边被赋值
printf("x = %d", x); //10。修改的其实是x
右值
int func()
{
int x = 9;
return x;
}
func() = 10; //编译器报错
//你只能:
int y = func(); //func()返回的其实是临时变量,也就是右值,然后将这个右值赋给y(逻辑上可以这样理解没问题,编译器不一定按这个步骤来,请看示例二)
示例一,先简单测试一下:
using std::string;
static string sStr = "ref";
string & getNameRef()
{
return sStr;
}
string getName()
{
return "yxy";
}
void printReference(const string & str)
{
printf("lv: %s\n", str.c_str());
}
void printReference(string && str)
{
printf("rv: %s\n", str.c_str());
}
调用:
string str = "james";
printReference(str); //调用左值版本重载
printReference(getNameRef()); //由于getNameRef返回的是左值,所以调用的是左值版本重载
printReference(getName()); //调用右值版本重载。即getName返回值是右值
示例二、根据这个特性,我们来试着简单实现std::string的部分功能,并着力减少不必要的buff分配
先看一下std::move的实现,后面要用到
template<class _Ty>
inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
{ // forward _Arg as movable
return ((typename remove_reference<_Ty>::type&&)_Arg);
}
返回类型实际为_Ty&&类型,&&指明了返回值为右值引用
class CXyString
{
/* 用于交换对象内容 */
friend void swapObj(CXyString & o1, CXyString & o2);
//也可以直接在这里实现这个函数
private:
char * m_buff;
size_t m_cap;
size_t m_size;
public:
/* 0 无参构造函数 */
CXyString() : m_buff(nullptr), m_size(0), m_cap(0)
{
printf("[CREATE EMPTY] %p.\n", this);
}
/* 1 构造函数 */
CXyString(const char * str, size_t len = 0) : CXyString()
{
printf("created, set data\n");
if (str)
append(str, len);
}
/* 2 左值引用构造函数,这里不可以写成传值类型的参数,会导致调用重载不明确 */
CXyString(const CXyString & o) : CXyString(o.data(), o.size())
{
printf("deep copy completed!!!\n");
}
/* 3 右值引用构造函数 */
CXyString(CXyString && o) : CXyString() //一定要调用无参构造函数以清空成员变量,不然交换完之后释放右值时会导致释放m_buff所指向的无效地址(vs中debug状态下为0xcccccccc)
{
printf("created, move data.\n");
swapObj(*this, o); //交换数据内容
}
/* 左值版本的赋值 */
CXyString & operator=(const CXyString & o) //值传递,以达到传参前自动调用复制构造函数的目的
{
printf("entered operator=\n");
if (&o == this)
return *this;
CXyString tmp(o); //复制一个新的,再交换。因为左值引用参数不能影响参数原来的内容
swapObj(*this, tmp);
printf("leaving operator=\n");
return *this;
}
/* 右值版本的赋值 */
CXyString & operator=(CXyString && o)
{
printf("entered operator=&&\n");
if (&o == this)
return *this;
swapObj(*this, o); //直接交换,右值参数是可以修改的,因为没有任何人能引用到传进来的右值参数(其实是个临时变量,用完即弃的那种)
printf("leaving operator=&&\n");
return *this;
}
/* 连接字符串,传入内存地址 */
CXyString operator+(const char * str)
{
printf("entered operator+\n");
//if (str == nullptr)
// return CXyString(*this); // return std::move(CXyString(*this)); //会被RVO,vs中测试直接return的可以不move,不会再复制一份
CXyString & ret = CXyString(*this); //ret可以是引用也可以为非引用,即使为非引用也不会再复制一份给ret
ret.append(str);
printf("leaving operator+\n");
//避免深复制,std::move返回&&右值引用,接着return会调用本类的右值引用版构造函数来转移m_buff中的数据而非复制。因为ret参数在本函数结束时会被释放
return std::move(ret);
}
/* 连接字符串,传入对象 */
CXyString operator+(const CXyString & o)
{
return operator+(o.data());
}
/* 析构 */
virtual ~CXyString()
{
printf("[DELETE] %p!\n", this);
clear();
}
const char * data() const
{
return m_buff;
}
const char * c_str() const
{
return m_buff;
}
size_t size() const
{
return m_size;
}
size_t length() const
{
return m_size;
}
size_t cap() const
{
return m_cap;
}
bool empty() const
{
return m_size == 0;
}
void clear()
{
if (m_buff) {
delete m_buff;
m_buff = nullptr;
m_size = 0;
}
}
void reserve(size_t n)
{
if (n + 1 <= m_cap)
return;
char * tmpBuff = new char[n + 1];
tmpBuff[0] = '\0';
if (m_size > 0) {
memcpy(tmpBuff, m_buff, m_size <= n ? m_size : n);
tmpBuff[n] = '\0';
delete m_buff;
}
m_buff = tmpBuff;
m_cap = n + 1;
}
void append(const char * str, size_t n = 0)
{
if (n == 0)
n = strlen(str);
if (n == 0)
return;
if (m_cap < m_size + n + 1) {
reserve(m_size + n + 1);
}
memcpy(m_buff + m_size, str, n);
m_buff[m_size + n] = '\0';
m_size += n;
}
void append(const CXyString & o)
{
append(o.data(), o.size());
}
};
/* 用于交换对象内容 */
static void swapObj(CXyString & o1, CXyString & o2) //类外friend函数,也可以在类内直接实现
{
std::swap(o1.m_buff, o2.m_buff); //std::swap用一来交换两个变量
std::swap(o1.m_cap, o2.m_cap);
std::swap(o1.m_size, o2.m_size);
}
调用测试:
CXyString getXyName()
{
return "yxy"; //调用的是1号构造函数
}
int main()
{
CXyString s1("I am str1."); //调用1号构造函数
CXyString s2 = "i am str2."; //调用1号构造函数
CXyString s3(s2); //调用2号构造函数(即左值版)
s3 = s2; //调用s3.operator=的左值引用版重载,会执行深复制,因为不能改变身为左值的s2
//按道理应该调用右值版本的复制构造函数,但实际上并不是这样,他不会调用任何构造函数
//将返回值传给s4时不会调用任何构造函数,会直接把返回值指定给s4,
//也就是说getXyName的return语句实际上构造的就是s4(调用的是1号构造函数) 这就是编译器的RVO(返回值优化)的作用
CXyString s4(getXyName());
s4 = s1 + s2; //先调用s1.operator+,由于其返回值是非引用的、临时的,不可被修改,也就是右值,所以然后会调用s4.operator=的右值版重载进行数据转移
s4 = s4 + "_end"; //先调用s4.operator+,因为返回值是右值(和上一句一样的道理),所以然后会调用s4.operator=的右值版重载进行数据转移
}
结尾:
仅模仿了std::string的部分功能,旨在理解右值引用的概念和实际用途。