在C ++ 11中声明接口的最佳方法

众所周知,一些语言有接口的概念。 这是Java:

public interface Testable { void test(); } 

我怎样才能以最紧凑的方式在C ++(或C ++ 11)中实现这一点,而且代码噪声很小? 我将不胜感激一个解决scheme,不需要一个单独的定义(让头是足够的)。 这是一个非常简单的方法,即使我find越野车;-)

 class Testable { public: virtual void test() = 0; protected: Testable(); Testable(const Testable& that); Testable& operator= (const Testable& that); virtual ~Testable(); } 

这只是一个开始,而且已经更长了,我想要的。 如何改善? 也许在这个std命名空间的某个地方有一个基类呢?

关于什么:

 class Testable { public: virtual ~Testable() { } virtual void test() = 0; } 

在C ++中,这不会影响子类的可复制性。 所有这一切说的是,孩子必须执行test (这正是你想要的接口)。 你不能实例化这个类,所以你不必担心任何隐含的构造函数,因为它们不能直接作为父接口的types被调用。

如果你想强制这个子类实现一个析构函数,你也可以使它成为纯粹的(但你仍然需要在界面中实现)。

另外请注意,如果你不需要多态破坏,你可以select使你的析构函数保护非虚拟。

对于dynamic(运行时)多态,我build议使用非虚拟接口 (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 */ } }; 

dynamic多态性的好处在于,您可以在运行时传递任何派生类,其中指向接口基类的指针或引用是预期的。 运行时系统将自动this指针从其静态基types向下转换为其dynamic派生types,并调用相应的实现(通常通过指向虚函数的表发生)。

对于静态(编译时多态),我build议使用奇怪的循环模板模式 (CRTP)。 这是相当复杂的,因为从基础到派生dynamic多态的自动向下转换必须由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 */ } }; 

这仍然允许你有相同接口的多个实现,但是你需要在编译时知道你正在调用哪个实现。

那么何时使用哪种forms? 这两种forms都可以让你重新使用一个通用接口,并在接口类中注入前/后条件testing。 dynamic多态的优点是你有运行时的灵活性,但你在虚拟函数调用(通常是通过一个函数指针的调用,很less有机会内联)支付。 静态的polymporhism是这样的镜像:没有虚函数调用开销,但缺点是,你需要更多的样板代码,你需要知道你在编译时调用。 基本上是效率/灵活性的权衡。

注意:对于编译时间,你也可以使用模板参数。 通过CRTP习惯用法的静态接口和普通模板参数的区别在于CRTPtypes接口是显式的(基于成员函数),而模板接口是隐式的(基于有效expression式)

通过用structreplace单词class ,所有的方法默认都是公共的,你可以保存一行。

没有必要使构造函数受到保护,因为无论如何你都不能用纯虚方法实例化一个类。 这也适用于复制构造函数。 编译器生成的默认构造函数将是空的,因为您没有任何数据成员,并且完全足够您的派生类。

由于编译器生成的运算符肯定会做错误的事情,所以你是关心=运算符的理由。 在实践中,从来没有人担心,因为将一个接口对象复制到另一个接口对象是没有意义的 这不是一个普遍发生的错误。

可inheritance类的析构函数应该总是公开的,虚拟的,或者是保护的,非虚拟的。 在这种情况下,我更喜欢公开和虚拟。

最后的结果只有一行比Java相当长:

 struct Testable { virtual void test() = 0; virtual ~Testable(); }; 

根据Scott Meyers(Effective Modern C ++)的说法:当声明接口(或多态基类)时,你需要虚析构函数,以获得适当的操作结果,例如通过基类指针或引用访问的派生类对象上的deletetypeid

 virtual ~Testable() = default; 

但是,用户声明的析构函数会抑制移动操作的生​​成,因此为了支持移动操作,您需要添加:

 Testable(Testable&&) = default; Testable& operator=(Testable&&) = default; 

声明移动操作会禁用复制操作,您还需要:

 Testable(const Testable&) = default; Testable& operator=(const Testable&) = default; 

最终的结果是:

 class Testable { public: virtual ~Testable() = default; // make dtor virtual Testable(Testable&&) = default; // support moving Testable& operator=(Testable&&) = default; Testable(const Testable&) = default; // support copying Testable& operator=(const Testable&) = default; virtual void test() = 0; }; 

请记住,如果不是pipe理指针,句柄和/或所有数据成员都有自己的析构函数来pipe理任何清理工作,那么“三项规则”是不必要的。 同样在虚拟基类的情况下,因为基类永远不能被直接实例化,所以如果你想要做的是定义一个没有数据成员的接口,就没有必要声明一个构造函数。编译器默认就好了。 如果您计划在接口types的指针上调用delete ,则需要保留的唯一项目是虚拟析构函数。 所以实际上你的界面可以像下面这样简单:

 class Testable { public: virtual void test() = 0; virtual ~Testable(); }