【问题标题】:Why no inheritance relations between the C++ <mutex> mutex'es?为什么 C++ <mutex> 互斥体之间没有继承关系?
【发布时间】:2015-07-24 20:24:51
【问题描述】:

C++标准库有以下四个类defined in

人们会假设这四个类之间存在钻石继承关系,或者递归性和时间性将是抽象基类。为什么这些都不是呢? 编辑:这只是性能考虑还是我缺少的语义?

【问题讨论】:

  • 继承有什么好处?
  • @T.C.:语义清晰。此外,使用基类函数指针的运行时而不是编译时多态性。我并不是说这些是巨大的好处,我只是在问没有继承任何东西的原因是否是因为性能。
  • 类层次结构强制timed_mutex 具有与mutex 相同的成员。但是这两个类的实现可能完全不同。为了适应这一点,您必须将它们定义为接口,以及这些接口之间的关系。接口 => 纯虚函数 => 没有适当的值语义。 (不确定是否可以通过仅由最派生类型管理的 PIMPL 隐藏数据成员。)

标签: multithreading c++11 inheritance mutex idioms


【解决方案1】:

虚拟函数和继承具有运行时影响,包括成本。

在 C++ 中,规则是不用为不用的东西付费。如果有这样的继承,成本将落在所有用户身上,甚至是不需要继承的用户。

如果你需要这样的继承,你可以删除 Lockable、BasicLockable 等的概念。如果你想(比如说)接受任何 Lockable 类型传递给一个函数,并在其中锁定输入。

这是一个可以存储任何BasicLockable类型的类:

template<class T>struct tag{using type=T;};

struct lockable {
  struct iimpl {
    virtual ~iimpl() {}
    virtual void lock() = 0;
    virtual void unlock() = 0;
    virtual bool try_lock() = 0;
  };
  template<class M>
  struct impl {
    M m;
    virtual void lock() override { m.lock(); }
    virtual void unlock() override { m.unlock(); }
    virtual bool try_lock() override { return m.try_lock(); }
  };
  std::unique_ptr<iimpl> pimpl;
  template<class M, class...Args>
  lockable( tag<M>, Args&&...args ):
    pimpl( new impl<M>{ {std::forward<Args>(args)...} } )
  {} 
  lockable(lockable&&)=default;
  lockable& operator=(lockable&&)=default;
  lockable()=default;
  explicit operator bool() const { return static_cast<bool>(pimpl); }
  void lock() { pimpl->lock(); }
  void unlock() override { pimpl->unlock(); }
  bool try_lock() override { return pimpl->try_lock(); }
};

它让你有一个函数采用任何mutex 类型之一。在实践中,您可能想要一个lockable_view,它是非拥有的:

struct lockable_view {
  void* pmutex = nullptr;
  struct vtable {
    void(*lock)(void*);
    void(*unlock)(void*);
    bool(*try_lock)(void*);
    template<class M>
    static impl const* get_table() {
      static const impl t={
        [](void* m){static_cast<M*>(m)->lock();},
        [](void* m){static_cast<M*>(m)->unlock();},
        [](void* m)->bool{return static_cast<M*>(m)->try_lock();}
      };
      return &t;
    }
  };
  vtable const* pimpl=nullptr;
  template<class M>
  lockable_view( M&& m ):
    pmutex( std::addressof(m) ),
    pimpl( vtable::get_table<std::decay_t<M>>() )
  {} 
  lockable_view(lockable&&)=default;
  lockable_view& operator=(lockable_view&&)=default;
  lockable_view()=default;
  explicit operator bool() const { return static_cast<bool>(pimpl); }
  void lock() { pimpl->lock(pmutex); }
  void unlock() { pimpl->unlock(pmutex); }
  bool try_lock() { return pimpl->try_lock(pmutex); }
};

这类似于在虚拟的情况下采用std::mutex&amp;。它使用不同类型的擦除技术(以避免不必要的分配)。我基本上创建了一个手动 vtable,并将指向它的指针存储在 pimpl 中。

在未来的 C++ 版本中,我们努力使这种类型擦除更容易。如您所见,目前有点痛苦。

【讨论】:

  • 这很有趣,尤其是我不知道的std::decay_t,所以+1。但是 - 你是说缺乏继承的唯一原因是性能,如果惩罚无关紧要,继承就会到位?
  • @einpok 我不能保证,但这是一个充分的理由。我相信 io 之外的std 中的 nothing 在其接口中使用继承/虚拟函数。我可能遗漏了一些东西,但std 旨在提供具有高级抽象和安全性的手工编码 C 级性能:虚函数不符合条件。还要注意std io ...好吧,糟糕,性能明智。
  • 如何使用这种设计调用try_lock_until
  • @HowardHinnant 哪个设计,lockable_view 一个?你写了一个timed_lockable_view 类也暴露了那个方法?如果你真的想要,你可以通过搞乱vtable 的构造方式(从初始化中分离分配,利用布局兼容性等)来减少代码重复。
  • 好吧,我仍然想知道除了性能之外是否还有其他原因......相应地编辑了问题。不过,谢谢。
猜你喜欢
  • 2021-06-04
  • 2011-02-24
  • 1970-01-01
  • 1970-01-01
  • 2014-02-28
  • 1970-01-01
  • 1970-01-01
  • 2014-06-30
  • 2017-01-03
相关资源
最近更新 更多