C++类的特种函数生成机制详解

编辑: admin 分类: c#语言 发布时间: 2021-12-12 来源:互联网
目录
  • C++类的特种函数生成机制
    • 规则
    • 例子:A BUG
    • 例子:std::mutex和std::thread
  • 题外话:为什么std::mutex不可移动?
    • 总结

      C++类的特种函数生成机制

      规则

      参考Effective Morder C++上的说明:

      • 默认构造函数:仅当类中不包含用户声明的构造函数时才生成。
      • 析构函数:默认生成,当基类的析构函数为虚时,派生类的默认析构函数为虚函数。
      • 拷贝构造函数:仅当类中不包含用户声明的拷贝构造函数时才生成。如果该类声明了移动操作,那么拷贝构造函数将被定义为删除的。
      • 拷贝赋值运算符:仅当类中不包含用户声明的拷贝赋值运算符时才生成。如果该类声明了移动操作,那么拷贝赋值运算符将被定义为删除的。
      • 移动构造函数和移动赋值运算符:仅当类中不包含用户声明的拷贝操作、移动操作和析构函数时才生成。

      例子:A BUG

      因为不熟悉析构函数的生成机制,导致了一个BUG。

      首先,下面的代码没有问题,因为数据成员m_,所以Widget默认也是个只移型别;mm中也可以插入一个由只移型别构造的std::pair<int, Widget>,因为pair默认支持右值参数构造(可以由只移的Widget构造)和自身的移动构造函数(可以移动构造到unordered_map中):

      class Widget {
      public:
          Widget() = default;
      //    ~Widget() = default;
      private:
          std::thread m_; // 只移型别
      };
      unordered_map<int, Widget> mm;
      mm.insert({12, Widget()});
      

      然后,我手贱加了一个默认的析构函数:

      class Widget {
      public:
          Widget() = default;
          ~Widget() = default;
      private:
          std::thread m_; // 只移型别
      };
      unordered_map<int, Widget> mm;
      mm.insert({12, Widget()}); // error!
      

      报错信息极长,核心错误是:

      error: no matching function for call to ‘std::unordered_map<int, Widget>::insert(<brace-enclosed initializer list>)'
         45 |     unordered_map<int, Widget> mm;
      

      可以把std::pair的构造单独抽出来看到更清晰的报错信息:

      // 代码如下:
      make_pair(12, Widget());
      // 报错如下:
      In template: no matching constructor for initialization of '__pair_type' (aka 'pair<int, Widget>')
      

      “显然”,是因为Widget的移动构造函数被隐式删除了(它既不能拷贝也不能移动了),所以无法由Widget参数构造一个std::pair。

      解决方案就是不要定义析构函数,或者显式定义一个移动构造函数:

      class Widget {
      public:
          Widget() = default;
          Widget(Widget&&) = default;
          ~Widget() = default;
      private:
          std::thread m_; // 只移型别
      };
      unordered_map<int, Widget> mm;
      mm.insert({12, Widget()});
      

      例子:std::mutex和std::thread

      在我做试验的时候,一开始错把std::mutex记成了只移型别

      定义了一个这样的类:

      class Widget {
      public:
          Widget() = default;
      private:
          std::mutex m_;
      };
      unordered_map<int, Widget> mm;
      mm.insert({12, Widget()}); // error!
      

      甚至在我没有添加析构函数的时候Widget就不能拷贝和移动了。

      看看源码:

      class mutex : private __mutex_base
      {
        public:
          /* ... */
          mutex() noexcept = default;
          ~mutex() = default;
          mutex(const mutex&) = delete;
          mutex& operator=(const mutex&) = delete;
      	  /* ... */
      }
      

      显然,因为mutex自行定义了默认的析构函数而且把拷贝构造函数定义为删除的,那么它的移动构造函数也会被隐式删除,所以mutex既不能拷贝也不能移动。

      和std::thread源码比较一下:

      class thread
      {
        public:
        thread() noexcept = default;
        thread(const thread&) = delete;
        thread(thread&& __t) noexcept
        { 
          swap(__t);
        }
        ~thread()
        {
          if (joinable())
            std::terminate();
        }
      }
      

      虽然std::thread定义了析构函数和删除的拷贝构造函数,但是它显式定义了移动构造函数,这使得它虽然不能拷贝但是可以移动。

      题外话:为什么std::mutex不可移动?

      大体来说就是std::mutex一般由多个线程调用,那么如果它的位置可以变化,那么怎么让所有线程都知道它的新位置在哪里呢?

      详见stackoverflow: https://stackoverflow.com/questions/7557179/move-constructor-for-stdmutex

      总结

      本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注海外IDC网的更多内容!

      【文章转自:中东服务器