最近突然发现了这个问题,挺有意思的,记录下来备忘。
以下代码在gcc 4.8.1下编译测试。
测试类结构如下:
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 : ""; }
protected:
char* _blocks; // 保存字符串的缓冲区
};
可以看到,这个类中包含了C++11标准中规定的若干元素:
默认构造函数(可缺省);参数构造函数(可缺省);析构函数;copy构造函数;move构造函数(转移构造函数);copy赋值运算符;move赋值运算符(转移赋值运算);对象交换函数; 其中,关键的几个函数实现如下:/**
* 参数构造器
* @param [in] str 字符串值
*/
Test2::Test2(const char* str) :
_blocks(NULL)
{
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);
}
突然想了解一下具有move构造函数和move赋值运算的类,在对象传递时会发生什么情况,所以写了下面的几个函数进行测试。
第一个函数,返回函数内部产生的局部变量:
/**
* 测试返回内部具备变量
* @return 返回临时生成的对象
*/
Test2 return_object()
{
Test2 res = "test";
return res;
} 通过如下代码测试
Test2 t1 = return_object(); t1 = return_object();结论: 第一行代码中,只在调用函数内部执行了一次参数构造函数(构造局部对象), 没有发生copy构造函数(或者move构造函数)的调用,即可以认为在函数内部实例化的局部对象就是返回值变量t1,这应该是编译器优化的结果;第二行代码执行时,变量t1已经被初始化,所以赋值运算是必然会发生的,此时除过在调用函数内部执行了一次参数构造函数(构造局部对象)外,还执行了一次move赋值运算,可见编译器认为函数的返回值是右值。由于有了move赋值运算符,所以没有调用copy赋值运算符,相当于将函数内部的局部对象(右值)转移到了t1变量(左值)中,完成了右到左的转化(减少了一次构造和析构);
第二个函数,返回函数内部产生的局部变量的引用:
/**
* 测试返回局部变量的引用
* @return 返回临时生成的对象的引用
*/
Test2& return_reference()
{
Test2 res = "test";
return res;
} 这个函数一看就是
错误的,返回局部变量的引用或指针都是不允许的,因为在函数返回前,局部变量就会被析构,导致返回的引用是
无效引用(已经游离),为了测试的完整性,用如下代码测试:
Test2 t2 = return_reference(); t2 = return_reference();结论: 第一行代码中,在调用函数内部执行了参数构造函数构造了局部对象,之后又执行了copy构造函数,其含义是将返回的局部对象引用,通过copy构造函数来构造变量t2对象,但结果是变量t2不一定可以构造成功,即使构造成功了其值也不正确,显然在调用copy构造函数的时候,局部对象已经析构,copy的值无效;第二行代码中,在调用函数内部执行了参数构造函数构造了局部对象,之后又执行了copy赋值函数,结果和第一行代码类似;
第三个函数,返回函数内部产生局部变量的右值引用:
/**
* 测试返回局部变量的右值引用
* @return 返回临时生成的对象的右值引用
*/
Test2 return_right_reference()
{
Test2 res = "test";
return std::move(res); // move函数在这里的作用是将res的引用类型转换为右值引用类型
}
以如下代码进行测试:
Test2 t3 = return_right_reference(); t3 = return_right_reference();结论: 第一行代码中,除了调用参数构造函数构造局部对象外,还调用了一次move构造函数,这是由于返回值变成了局部对象的右值引用,和变量t3类型不同,所以又额外的调用了一次move构造函数对变量t3进行初始化;第二行代码中,情况就比较复杂了。照例通过参数构造函数构造了局部对象,但返回的是其右值引用,所以又调用了一次move构造函数,通过该右值引用产生了一个临时的Test2对象(右值对象),最后通过一个move赋值运算将临时的Test2对象转移给变量t3;
第四个函数,对第三个函数进行修改:
/**
* 测试返回局部变量的右值引用
* @return 返回临时生成的对象的右值引用
*/
Test2&& return_right_reference2()
{
Test2 res = "test";
return std::move(res); // move函数在这里的作用是将res的引用类型转换为右值引用类型
}
结论:
这段代码执行的结果和“第二个函数”一样,返回局部