• C++中noncopyable不可拷贝类的使用


    背景:项目中需要某个类不能被拷贝构造和赋值构造,下面举例说明该场景:

    什么时候需要不可拷贝类

    考虑一种情况,我们要实现一个含有动态数组成员的类,其中动态数组成员在构造函数中 new 出来,在析构函数中 delete 掉。比如说这样一个矩阵类:

    template
    class Matrix {
    public:
       int w;
       int h;
       _T* data;
       
       // 构造函数
       Matrix(int _w, int _h): w(_w), h(_h){
          data = new  _T[w*h];
       }
     
       // 析构函数
       ~Matrix() {
           delete [] data;
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    上面的测试 1 中,我们先构造了 m1m2 两个 Matrix 实例,这意味着他们各自开辟了一块动态内存来存储矩阵数据。然后我们使用 =m2 拷贝给 m1,这时候 m1 的每个成员(whdata)都被各自使用 = 运算符拷贝为和 m2 相同的值。m1.data 是个指针,所以就和 m2.data 指向了同一块的内存。于是这里就会出现两个问题:其一, 发生拷贝前 m1.data 指向的动态内存区在拷贝后不再拥有指向它的有效指针,无法被释放,于是发生了内存泄露;其二,在 copy() 结束后,m1m2 被销毁,各自调用析构函数,由于他们的 data 指向同一块内存,于是发生了双重释放。

    测试 2 中也有类似问题。当调用 copy(Matrix<_T> cpy) 时,形参 cpy 拷贝自实参,而 cpy 会在函数结束时销毁,cpy.data 指向的内存被释放,所以实参的矩阵数据也被销毁了——这显然是我们不愿意看见的。同样的,在返回时,ret 随着函数结束而销毁,返回值因为拷贝自 ret,所以其矩阵数据也被销毁了。

    因此,对于像 Matrix 这样的类,我们不希望这种拷贝发生。一个解决办法是重载拷贝函数,每次拷贝就开辟新的动态内存:

    Matrix<_T>& operator = (const Matrix<_T>& cpy) {
        w = cpy.w;
        h = cpy.h;
        delete []  data;
        data = new _T[w*h];
        memcpy(data, cpy.data, sizeof(_T)*w*h);
        return *this;
    }
     
    Matrix(const Matrix<_T>& cpy):w(cpy.w), h(cpy.h) {
        data = new _T[w*h];
        memcpy(data, cpy.data, sizeof(_T)*w*h);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这样做也有不好的地方。频繁开辟动态内存,当数据量很大时(比如图像处理),对程序性能是有影响的。在接口设计的角度考虑,应该把这种拷贝操作以较明显的形式提供给用户,比如禁用等号拷贝,以直接的函数代替 = 操作:

    void copyFrom(const Matrix<_T>& cpy) {
        w = cpy.w;
        h = cpy.h;
        delete []  data;
        data = new _T[w*h];
        memcpy(data, cpy.data, sizeof(_T)*w*h);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    再禁用构造拷贝,只允许用户以引用传递的办法在自定义函数中使用 Matrix 类。

    那么,如何禁止拷贝操作呢?

    实现不可拷贝类

    使用 boost::noncopyable

    Boost 作为 C++ 万金油工具箱,在 下提供了不可拷贝类的实现,使用起来也非常简单,让自己的类继承自 boost::noncopyable 即可:

    class Matrix : boost::noncopyable
    {
        // 类实现
    }
    
    • 1
    • 2
    • 3
    • 4

    声明拷贝函数为私有

    如果不想用第三方库,自己实现呢?不妨先看一下 Boost 是怎么做的:

    private:  // emphasize the following members are private
        noncopyable( const noncopyable& );
        noncopyable& operator=( const noncopyable& );
    
    • 1
    • 2
    • 3

    嗯,****直接把拷贝函数声明为私有****的不就等于禁用了么,so smart!于是:

    template
    class Matrix 
    {
    private:
        Matrix(const Matrix<_T>&);
        Matrix<_T>& operator = (const Matrix<_T>&);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    C++ 11 下使用 delete 关键字

    C++ 11 中为不可拷贝类提供了更简单的实现方法,使用 delete 关键字即可

    template
    class Matrix
    {
    public:
    Matrix(const Matrix<_T>&) = *delete*;
    Matrix<_T>& operator = (const Matrix<_T>&) = *delete*;

    }

    关于类似 Matrix 矩阵类的实现,更高级的做法是像智能指针一样封装其内部数据,用内部计数器来确定动态分配的成员是否要释放掉,不过这是另外一个问题了。

    boost::noncopyable比较简单, 主要用于单例的情况.

    **通常情况下, 要写一个单例类就要在类的声明把它们的构造函数, 赋值函数, 析构函数, 复制构造函数隐藏到private或者protected之中, 每个类都这么做麻烦**.
    
    • 1

    ​ 有noncopyable类, 只要让单例类直接继承noncopyable.
    ​ class noncopyable的基本思想是把构造函数和析构函数设置protected权限,这样子类可以调用,但是外面的类不能调用,那么当子类需要定义构造函数的时候不至于通不过编译。但是最关键的是****noncopyable把复制构造函数和复制赋值函数做成了private****,这就意味着除非子类定义自己的copy构造和赋值函数,否则在子类没有定义的情况下,外面的调用者是不能够通过赋值和copy构造等手段来产生一个新的子类对象的。

    private: // emphasize the following members are private
    noncopyable( const noncopyable& );
    const noncopyable& operator=( const noncopyable& );

     
    #ifndef BOOST_NONCOPYABLE_HPP_INCLUDED
    #define BOOST_NONCOPYABLE_HPP_INCLUDED
     
    namespace boost {
     
    //  Private copy constructor and copy assignment ensure classes derived from
    //  class noncopyable cannot be copied.
     
    //  Contributed by Dave Abrahams
     
    namespace noncopyable_  // protection from unintended ADL
    {
      class noncopyable
      {
       protected:
          noncopyable() {}
          ~noncopyable() {}
       private:  // emphasize the following members are private
          noncopyable( const noncopyable& );
          const noncopyable& operator=( const noncopyable& );
      };
    }
     
    typedef noncopyable_::noncopyable noncopyable;
     
    } // namespace boost
     
    #endif  // BOOST_NONCOPYABLE_HPP_INCLUDED
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
     
    #include "tfun.h"
     
    class myclass: public boost::noncopyable
    {
    public:
        myclass(){};
        myclass(int i){};
    };
     
    int main()
    {
        myclass cl1();
        myclass cl2(1);
     
        // myclass cl3(cl1);    // error
        // myclass cl4(cl2);    // error
     
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
  • 相关阅读:
    【开源】基于Vue.js的音乐偏好度推荐系统的设计和实现
    Linux执行jps命令的时候报错:-bash: jps: command not found
    防止被00后整顿?一公司招聘要求员工不能起诉公司
    Java-多线程基础及线程安全
    Flink异步I/O算子原理解析+源码详解
    手机云便签待办分类内容怎么排序?
    ik分词器的基本使用
    计网小题题库整理第一轮(面向期末基础)(3)
    基于Django的博客系统之用HayStack连接elasticsearch增加搜索功能(五)
    「元宇宙」成为发展新坐标,文化产业如何「沉浸式」升维?
  • 原文地址:https://blog.csdn.net/weixin_45312249/article/details/134467395