在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式)
通过用struct
replace单词class
,所有的方法默认都是公共的,你可以保存一行。
没有必要使构造函数受到保护,因为无论如何你都不能用纯虚方法实例化一个类。 这也适用于复制构造函数。 编译器生成的默认构造函数将是空的,因为您没有任何数据成员,并且完全足够您的派生类。
由于编译器生成的运算符肯定会做错误的事情,所以你是关心=
运算符的理由。 在实践中,从来没有人担心,因为将一个接口对象复制到另一个接口对象是没有意义的 这不是一个普遍发生的错误。
可inheritance类的析构函数应该总是公开的,虚拟的,或者是保护的,非虚拟的。 在这种情况下,我更喜欢公开和虚拟。
最后的结果只有一行比Java相当长:
struct Testable { virtual void test() = 0; virtual ~Testable(); };
根据Scott Meyers(Effective Modern C ++)的说法:当声明接口(或多态基类)时,你需要虚析构函数,以获得适当的操作结果,例如通过基类指针或引用访问的派生类对象上的delete
或typeid
。
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(); }