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

右值引用和移动语义

在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)

可以看出两处不同:
  1. g++对return_local()函数进行了优化。
  2. g++对return_local_member()函数进行不正确的优化,所以应该避免这种怪癖的写法。

总结

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

扩展阅读

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值