虚拟函数和继承具有运行时影响,包括成本。
在 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&。它使用不同类型的擦除技术(以避免不必要的分配)。我基本上创建了一个手动 vtable,并将指向它的指针存储在 pimpl 中。
在未来的 C++ 版本中,我们努力使这种类型擦除更容易。如您所见,目前有点痛苦。