对于动态(运行时)多态性,我建议使用非虚拟接口(NVI) 习惯用法。这种模式保持接口非虚拟和公共,析构函数虚拟和公共,以及实现纯虚拟和私有
class DynamicInterface
{
public:
// non-virtual interface
void fun() { do_fun(); } // equivalent to "this->do_fun()"
// enable deletion of a Derived* through a Base*
virtual ~DynamicInterface() = default;
private:
// pure virtual implementation
virtual void do_fun() = 0;
};
class DynamicImplementation
:
public DynamicInterface
{
private:
virtual void do_fun() { /* implementation here */ }
};
动态多态性的好处是您可以在运行时传递任何派生类,其中需要指向接口基类的指针或引用。this
运行时系统会自动将指针从其静态基类型向下转换为其动态派生类型并调用相应的实现(通常通过带有指向虚函数的指针的表发生)。
对于静态(编译时多态性),我建议使用Curiously Recurring Template Pattern (CRTP)。这涉及更多,因为必须使用static_cast
. 此静态转换可以在每个静态接口派生自的帮助器类中定义
template<typename Derived>
class enable_down_cast
{
private:
typedef enable_down_cast Base;
public:
Derived const* self() const
{
// casting "down" the inheritance hierarchy
return static_cast<Derived const*>(this);
}
Derived* self()
{
return static_cast<Derived*>(this);
}
protected:
// disable deletion of Derived* through Base*
// enable deletion of Base* through Derived*
~enable_down_cast() = default; // C++11 only, use ~enable_down_cast() {} in C++98
};
然后你定义一个像这样的静态接口:
template<typename Impl>
class StaticInterface
:
// enable static polymorphism
public enable_down_cast< Impl >
{
private:
// dependent name now in scope
using enable_down_cast< Impl >::self;
public:
// interface
void fun() { self()->do_fun(); }
protected:
// disable deletion of Derived* through Base*
// enable deletion of Base* through Derived*
~StaticInterface() = default; // C++11 only, use ~IFooInterface() {} in C++98/03
};
最后,您创建了一个从接口派生的实现,并将其自身作为参数
class StaticImplementation
:
public StaticInterface< StaticImplementation >
{
private:
// implementation
friend class StaticInterface< StaticImplementation > ;
void do_fun() { /* your implementation here */ }
};
这仍然允许您拥有同一接口的多个实现,但您需要在编译时知道您正在调用哪个实现。
那么什么时候使用哪种形式呢?这两种形式都可以让您重用通用接口并在接口类中注入前置/后置条件测试。动态多态性的优点是您具有运行时灵活性,但您在虚函数调用中为此付出代价(通常通过函数指针进行调用,几乎没有内联的机会)。静态多态是其镜像:没有虚函数调用开销,但缺点是您需要更多样板代码,并且您需要知道在编译时调用的是什么。基本上是效率/灵活性的权衡。
注意:对于编译时多态,您也可以使用模板参数。通过 CRTP 成语的静态接口与普通模板参数的区别在于 CRTP 类型的接口是显式的(基于成员函数),而模板接口是隐式的(基于有效的表达式)