右值左值,左右值引用延申:移动语义的用途之一,减少拷贝和析构

原测试见

https://blog.csdn.net/mousebaby808/article/details/18060743

重新进行解释和测试的完整代码如下

#include<iostream>
#include<cstring>
using namespace std;
class Test2
{
public:
	Test2() {}
	Test2(const char* str);
	Test2(const Test2& o);
	Test2(Test2&& o);
	virtual ~Test2();
	Test2& operator=(const Test2& o);
	Test2& operator=(Test2&& o);
	void swap(Test2& o);
	const char* cstr() const { return _blocks ? _blocks : ""; }
   char getvalue() {return *_blocks;}
protected:
	char* _blocks;	// 保存字符串的缓冲区
   

};

/**
 * 参数构造器
 * @param [in] str 字符串值
 */
Test2::Test2(const char* str) :
	_blocks(NULL)
{
   cout<<"convert"<<endl;
	if (str)
		_blocks = ::strdup(str);
}
 
/**
 * 拷贝构造函数
 * @param [in] o 同类型的另一个对象引用
 */
Test2::Test2(const Test2& o) :
	_blocks(NULL)
{
	if (o._blocks)
		_blocks = ::strdup(o._blocks);
}
 
/**
 * Move构造函数
 * @param [in] o 同类型的另一个对象右值引用
 */
Test2::Test2(Test2&& o) :
	_blocks(NULL)
{
	swap(o);
}
 
/**
 * 析构函数
 */
Test2::~Test2()
{
	if (_blocks)
		::free(_blocks);
	_blocks = NULL;
}
 
/**
 * 赋值运算符重载
 * @param [in] o 同类型的另一个对象引用
 * @return 当前类型的另一个引用
 */
// Test2& Test2::operator=(const Test2& o)
// {
// 	if (this != &o)
// 		Test2(o, int()).swap(*this);
// 	return *this;
// }
 
/**
 * 右值引用赋值运算符重载
 * @param [in] o 同类型的另一个对象右值引用
 * @return 当前类型的另一个引用
 */
Test2& Test2::operator=(Test2&& o)
{
	if (this != &o)
	{
		swap(o);
		o.~Test2();
	}
	return *this;
}
 
/**
 * 交换两个对象
 * @param [in] o 同类型的另一个对象
 */
void Test2::swap(Test2& o)
{
	std::swap(_blocks, o._blocks);
}

/**
 * 测试返回内部具备变量
 * @return 返回临时生成的对象
 */
Test2 return_object()
{
	// Test2 res = "test";//隐式调用类型转换
   Test2 res("a"); 
	return res;
}
/**
 * 测试返回局部变量的引用
 * @return 返回临时生成的对象的引用
 */
// Test2& return_reference()
// {
// 	Test2 res = "test";
// 	return res;
// }

/**
 * 测试返回局部变量的右值引用
 * @return 返回临时生成的对象的右值引用
 */
Test2 return_right_reference()
{
	Test2 res = "test";
	return std::move(res);	// move函数在这里的作用是将res的引用类型转换为右值
}

/**
 * 测试返回局部变量的右值引用
 * @return 返回临时生成的对象的右值引用
 */
Test2&& return_right_reference2()
{
	Test2 res = "test";
	return std::move(res);	// move函数在这里的作用是将res的引用类型转换为右值
}

int main(int argc, char const *argv[])
{
	// Test2 t1 = return_object();
	// t1 = return_object();
   
//    Test2 t2 = return_reference();
//    t2 = return_reference();
	// Test2 t3 = return_right_reference();
	// t3 = return_right_reference();

	Test2 t4 = return_right_reference2();
	t4 = return_right_reference();
	cout<<t4.getvalue();
	return 0;
}

首先该测试的赋值运算符重载有问题,后面测试代码也没用到就注释了。

第一个函数,返回函数内部产生的局部变量:

函数体内 test2 res = 'test"
隐式的调用了类型转换构造函数,没有发生copy构造函数(或者move构造函数)的调用,即可以认为经过编译器的优化,“test”的值被自动转换为test2对象且即时赋值给了res,后面同理。

/**
 * 测试返回内部具备变量
 * @return 返回临时生成的对象
 */
Test2 return_object()
{
	Test2 res = "test";
	return res;
}

//如下代码测试
Test2 t1 = return_object();
t1 = return_object();

结论:
1.第一行代码中,return_object返回值时,编译器自动优化使用move移动语义将值赋给t1,结果应该和下方函数三一致;
2.第二行代码执行时,变量t1已经被初始化,所以赋值运算是必然会发生的,此时除过在调用函数内部执行了一次参数构造函数(构造局部对象)外,还执行了一次move赋值运算,可见编译器认为函数的返回值是右值。由于有了move赋值运算符,所以没有调用copy赋值运算符,相当于将函数内部的局部对象(右值)转移到了t1变量(左值)中,完成了右到左的转化(减少了一次构造和析构);

第二个函数,返回函数内部产生的局部变量的引用:

/**
 * 测试返回局部变量的引用
 * @return 返回临时生成的对象的引用
 */
Test2& return_reference()
{
	Test2 res = "test";
	return res;
}
/*这个函数一看就是 错误的,返回局部变量的引用或指针都是不允许的,
因为在函数返回前,局部变量就会被析构,导致返回的引用是 无效引用
(已经游离),为了测试的完整性,用如下代码测试:*/
//如下测试代码
Test2 t2 = return_reference();
t2 = return_reference();

第三个函数,返回函数内部产生局部变量的右值

Test2 return_right_reference()
{
	Test2 res = "test";
	return std::move(res);	// move函数在这里的作用是将res的引用类型转换为右值
}
//如下代码测试
Test2 t3 = return_right_reference();
t3 = return_right_reference();

结论:
第一行代码中,除了调用参数构造函数构造局部对象外,还调用了一次move构造函数,将局部对象的值move成右值即时给了出去;
第二行代码中,情况就比较复杂了。照例通过参数构造函数构造了局部对象,但返回的是其右值可以即时赋值给变量t3;

第四个函数,对第三个函数进行修改:

/**
 * 测试返回局部变量的右值引用
 * @return 返回临时生成的对象的右值引用
 */
Test2&& return_right_reference2()
{
	Test2 res = "test";
	return std::move(res);	// move函数在这里的作用是将res的引用类型转换为右值引用类型
}

结论:
  这段代码执行的结果和“第二个函数”一样,返回局部变量的引用(不管是左值还是右值)都不会有正确结果。应该说,只要是引用,析构函数就会执行把这个值释放掉的。实际测的时候也发生了这个情况。评论中关于认为函数四是正确的说法是错误的。

总结:

最后发现,“第一个函数”经过编译器的优化应该和第三个一样,但是实际应用过程中,遇到返回这种局部变量的值的,可以显式的把返回值用move转换,这种写法充分的利用了编译器在构造对象时进行的优化以及move赋值运算带来的优势,避免了对象在传递过程中产生的临时对象以及引发的构造和析构; 这也体现了move赋值运算存在的必要性。
无论如何,都不能在函数内部返回临时变量的指针或引用,无论该引用是左值引用还是右值引用。C++11也从来没有认为变量的控制权被转移后析构就不再发生了。所以要在函数内部产生一个对象并返回,正确的做法是:1)将对象建立在堆内存上并返回地址;2)返回局部对象,并通过copy复制运算符在函数外复制该局部对象的副本;3)返回局部对象(是一个右值),并通过move复制运算符将返回的局部对象转移到另一个对象中;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值