C++11右值引用实际使用示例

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的部分功能,旨在理解右值引用的概念和实际用途。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值