编写一个安全可靠的C++11类:引入移动语义

来源:互联网 发布:金山快译怎么汉化软件 编辑:程序博客网 时间:2024/06/11 18:54

右值引用和移动语义

在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-assignment函数    }    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_p的类型(在此为指针),优先使用专属swap,其次std::swap特化版本,最次使用std::swap        //详见《Effective C++》条款25        using std::swap;        swap(m_pImpl, a.m_pImpl);    }private:    Implemention * m_pImpl;};//在class A的同属命名空间中,定义专属swapvoid 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;}

上述代码运行结果
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----------constructorcopy assignmentcopy constructordestructor-------g----------constructormove constructordestructor-------h----------copy constructormove constructordestructor-------i----------move constructor-------j----------move constructor-------k----------constructorconstructormove assignmentdestructor-------l----------constructorcopy constructormove assignmentdestructor-------m----------constructormove assignment-------n----------constructormove constructordestructor-------o----------constructordestructormove constructor-------p----------destructordestructordestructordestructordestructordestructordestructordestructordestructordestructordestructordestructordestructordestructorD:\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就保持了对不支持右值引用的兼容性。

总结

源代码中有许多注释,其中包含了非常多的知识点和编程经验点,参考了一些书和网络资料。
可以看出,规范、高效、安全、高扩展性的代码,是很难写的,需要对细节的准确把握,以及对可靠编程经验的总结和学习。

扩展阅读

C++11 标准新特性: 右值引用与转移语义
c++11 函数内部返回对象使用move语义的最佳实践
RVO V.S. std::move
【编程篇】C++11系列之——临时对象分析
0 0