右值引用和移动语义
在C++11(即C++0x)中,引入了右值引用的概念,同时在STL中提供std::move函数。这个机制完善了C++中关于左值、右值、以及引用的概念,优雅完美地解决了临时变量效率的问题。另外std::forward实现了所谓的“完美转发”,在泛型编程中有很大的作用。
这篇文章,只对右值引用和移动语义做几个简单的实践。
测试代码
#include <iostream>
class Implemention {
//演示使用,留空。
//实践中,如果使用PIMPL手法,则通常将其声明在单独的头文件中。
};
class Interface {
public:
void test() {
std::cout << "test" << std::endl;
}
Interface() {
std::cout << "constructor" << std::endl;
m_pImpl = nullptr;
}
~Interface() {
std::cout << "destructor" << std::endl;
//无须 if(m_pImpl == nullptr) 判断,因为delete nullptr;合法
delete m_pImpl;
}
Interface(const Interface &a) {
std::cout << "copy constructor" << std::endl;
m_pImpl = new Implemention(*a.m_pImpl); //调用class Impl的copy-constructor函数
}
Interface(Interface &&a) {
std::cout << "move constructor" << std::endl;
m_pImpl = nullptr;
swap(a);
}
Interface & operator=(const Interface &a) {
std::cout << "copy assignment" << std::endl;
//证同测试:如果后续语句能保证“自我赋值”不出故障,则可以考虑省略“证同测试”。
//如果对象相同,那么提高了效率,否则降低了效率,需要综合考虑。
//详见《Effective C++》条款11
if(this == &a)
return *this;
Interface tmp(a);
swap(tmp);
return *this;
}
Interface & operator=(Interface &&a) {
std::cout << "move assignment" << std::endl;
//证同测试:说明同上
if(this == &a)
return *this;
m_pImpl = nullptr;
swap(a);
return *this;
}
void swap(Interface &a) throw() {
//保证无异常
//针对m_pImpl的类型(在此为指针),优先使用专属swap,其次std::swap特化版本,最次使用std::swap
//详见《Effective C++》条款25
using std::swap;
swap(m_pImpl, a.m_pImpl);
}
private:
Implemention * m_pImpl;
};
//在class A的同属命名空间中,定义专属swap
void swap(Interface & lh, Interface & rh)
{
lh.swap(rh);
}
//std::swap特化版本
namespace std {
template<>
void swap<Interface>(Interface & lh, Interface & rh) {
lh.swap(rh);
}
}
//返回临时对象时,依旧按照传统的写法,让编译器做优化,而非返回右值引用,这样可以提高兼容性。
//编译器会优先使用移动语义,其次使用RVO,最后才会copy-assignment。
Interface return_local()
{
Interface var;
return var;
}
//在返回临时对象的成员时,因为此时编译器不会做其他优化,所以需要显式指定返回右值引用。
Interface && return_local_member()
{
struct{
Interface m;
}tmp;
return std::move(tmp.m);
}
int main(int, char *[])
{
//constructor
Interface a;
std::cout << "-------a----------" << std::endl;
Interface b(Interface()); //不合法的写法,被编译器警告并忽略
std::cout << "-------b-----------" << std::endl;
//copy constructor
Interface c(a);
std::cout << "-------c----------" << std::endl;
Interface d(Interface(a)); //不合法的写法,被编译器警告并忽略
std::cout << "-------d-----------" << std::endl;
Interface e = a;
std::cout << "-------e----------" << std::endl;
Interface f = Interface(a); //不产生临时对象,直接被优化为与e相同
std::cout << "-------f----------" << std::endl;
//copy assignment
Interface g;
g = a;
std::cout << "-------g----------" << std::endl;
//move constructor
Interface h(std::move(Interface())); //产生临时对象
std::cout << "-------h----------" << std::endl;
Interface i(std::move(Interface(a))); //产生临时对象
std::cout << "-------i----------" << std::endl;
Interface j(std::move(a)); //保证此后不再使用a
std::cout << "-------j----------" << std::endl;
Interface k = std::move(a); //与j相同
std::cout << "-------k----------" << std::endl;
//move assignment
Interface l;
l = std::move(Interface()); //产生临时对象
std::cout << "-------l----------" << std::endl;
Interface m;
m = std::move(Interface(a)); //产生临时对象
std::cout << "-------m----------" << std::endl;
Interface n;
n = std::move(a); //保证此后不再使用a
std::cout << "-------n----------" << std::endl;
//return local
Interface o = return_local();
std::cout << "-------o----------" << std::endl;
Interface p = return_local_member();
std::cout << "-------p----------" << std::endl;
a;b;c;d;e;f;g;h;i;j;k;l;m;n;o;p; //使用上述变量,避免编译警告
return 0;
}
在Windows平台上使用微软C++编译器,上述代码运行结果
Starting D:\QtProjects\build-move-Desktop_Qt_5_7_1_MSVC2013_64bit-Debug\debug\move.exe...
constructor
-------a----------
-------b-----------
copy constructor
-------c----------
-------d-----------
copy constructor
-------e----------
copy constructor
-------f----------
constructor
copy assignment
copy constructor
destructor
-------g----------
constructor
move constructor
destructor
-------h----------
copy constructor
move constructor
destructor
-------i----------
move constructor
-------j----------
move constructor
-------k----------
constructor
constructor
move assignment
destructor
-------l----------
constructor
copy constructor
move assignment
destructor
-------m----------
constructor
move assignment
-------n----------
constructor
move constructor
destructor
-------o----------
constructor
destructor
move constructor
-------p----------
destructor
destructor
destructor
destructor
destructor
destructor
destructor
destructor
destructor
destructor
destructor
destructor
destructor
destructor
D:\QtProjects\build-move-Desktop_Qt_5_7_1_MSVC2013_64bit-Debug\debug\move.exe exited with code 0
另外,经过测试,如果上述代码中不定义move-constructor函数,那么编译器会调用copy-constructor函数;同样地,如果不定义move-assignment函数,那么编译器会调用copy-assignment函数。这样C++11就保持了对不支持右值引用的兼容性。
在Ubuntu/Linux平台上使用g++编译器,上述代码运行结果
monte@Monte-Ubuntu:~/Study/move$ ./a.out
constructor
-------a----------
-------b-----------
copy constructor
-------c----------
-------d-----------
copy constructor
-------e----------
copy constructor
-------f----------
constructor
copy assignment
copy constructor
destructor
-------g----------
constructor
move constructor
destructor
-------h----------
copy constructor
move constructor
destructor
-------i----------
move constructor
-------j----------
move constructor
-------k----------
constructor
constructor
move assignment
destructor
-------l----------
constructor
copy constructor
move assignment
destructor
-------m----------
constructor
move assignment
-------n----------
constructor
-------o----------
constructor
destructor
move constructor
Segmentation fault (core dumped)
可以看出两处不同:
- g++对return_local()函数进行了优化。
- g++对return_local_member()函数进行不正确的优化,所以应该避免这种怪癖的写法。
总结
源代码中有许多注释,其中包含了非常多的知识点和编程经验点,参考了一些书和网络资料。
可以看出,规范、高效、安全、高扩展性的代码,是很难写的,需要对细节的准确把握,以及对可靠编程经验的总结和学习。