你如何在C ++中声明一个接口?
我如何设置一个代表接口的类? 这只是一个抽象的基类吗?
为了扩展bradtgmurray的答案,你可能希望通过添加一个虚拟析构函数来对接口的纯虚拟方法列表进行一个例外。 这允许您将指针所有权传递给另一方,而不公开具体的派生类。 析构函数不需要做任何事情,因为接口没有任何具体的成员。 将函数定义为虚拟函数和内联函数似乎是矛盾的,但相信我 – 事实并非如此。
class IDemo { public: virtual ~IDemo() {} virtual void OverrideMe() = 0; }; class Parent { public: virtual ~Parent(); }; class Child : public Parent, public IDemo { public: virtual void OverrideMe() { //do stuff } };
你不必为虚拟析构函数包含一个主体 – 事实certificate,一些编译器在优化一个空的析构函数时遇到了麻烦,你最好使用默认的。
用纯虚拟方法做一个类。 通过创build另一个覆盖这些虚拟方法的类来使用该接口。
纯虚拟方法是一种被定义为虚拟并被分配为0的类方法。
class IDemo { public: virtual ~IDemo() {} virtual void OverrideMe() = 0; }; class Child : public IDemo { public: virtual void OverrideMe() { //do stuff } };
除了C#/ Java中的抽象基类之外,您还有一个特殊的Interfacetypes类的全部原因是因为C#/ Java不支持多重inheritance。
C ++支持多重inheritance,所以不需要特殊的types。 没有非抽象(纯虚拟)方法的抽象基类在function上等同于C#/ Java接口。
在C ++中本质上没有“接口”的概念。 AFAIK,接口最初是在Java中引入的,以解决缺乏多inheritance的问题。 这个概念已经certificate是相当有用的,通过使用一个抽象基类,在C ++中可以达到同样的效果。
抽象基类是一个类,其中至less有一个成员函数(Java语言中的方法)是使用以下语法声明的纯虚函数:
class A { virtual void foo() = 0; };
一个抽象的基类不能实例化,也就是说你不能声明一个类A的对象。你只能从A派生类,但是没有提供foo()
实现的派生类也是抽象的。 为了停止抽象,派生类必须为它所inheritance的所有纯虚函数提供实现。
请注意,抽象基类可能不仅仅是一个接口,因为它可以包含不是纯虚拟的数据成员和成员函数。 一个接口的等价物将是一个没有任何数据的抽象基类,只有纯虚函数。
而且,正如Mark Ransom指出的那样,抽象基类应该提供一个虚拟析构函数,就像任何基类一样。
至于我可以testing,添加虚拟析构函数是非常重要的。 我正在使用new
创build的对象和delete
销毁。
如果不在接口中添加虚拟析构函数,则不会调用inheritance类的析构函数。
class IBase { public: virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes virtual void Describe() = 0; // pure virtual method }; class Tester : public IBase { public: Tester(std::string name); virtual ~Tester(); virtual void Describe(); private: std::string privatename; }; Tester::Tester(std::string name) { std::cout << "Tester constructor" << std::endl; this->privatename = name; } Tester::~Tester() { std::cout << "Tester destructor" << std::endl; } void Tester::Describe() { std::cout << "I'm Tester [" << this->privatename << "]" << std::endl; } void descriptor(IBase * obj) { obj->Describe(); } int main(int argc, char** argv) { std::cout << std::endl << "Tester Testing..." << std::endl; Tester * obj1 = new Tester("Declared with Tester"); descriptor(obj1); delete obj1; std::cout << std::endl << "IBase Testing..." << std::endl; IBase * obj2 = new Tester("Declared with IBase"); descriptor(obj2); delete obj2; // this is a bad usage of the object since it is created with "new" but there are no "delete" std::cout << std::endl << "Tester not defined..." << std::endl; descriptor(new Tester("Not defined")); return 0; }
如果你运行前面的代码没有virtual ~IBase() {};
,你会看到析构函数Tester::~Tester()
永远不会被调用。
我的回答基本上与其他人一样,但我认为还有两件重要的事情要做:
-
在你的界面中声明一个虚拟析构函数,或者创build一个受保护的非虚拟析构函数,以避免有人试图删除一个
IDemo
types的对象。 -
使用虚拟inheritance来避免多重inheritance的问题。 (当我们使用接口时,更常见的是多重inheritance。)
和其他答案一样:
- 用纯虚拟方法做一个类。
-
通过创build另一个覆盖这些虚拟方法的类来使用该接口。
class IDemo { public: virtual void OverrideMe() = 0; virtual ~IDemo() {} }
要么
class IDemo { public: virtual void OverrideMe() = 0; protected: ~IDemo() {} }
和
class Child : virtual public IDemo { public: virtual void OverrideMe() { //do stuff } }
以上所有的好答案。 一个额外的事情,你应该记住 – 你也可以有一个纯粹的虚拟析构函数。 唯一的区别是你还需要实现它。
困惑?
--- header file ---- class foo { public: foo() {;} virtual ~foo() = 0; virtual bool overrideMe() {return false;} }; ---- source ---- foo::~foo() { }
你想要这样做的主要原因是,如果你想提供接口方法,像我一样,但重写它们是可选的。
为了使这个类成为一个接口类需要一个纯粹的虚拟方法,但是你所有的虚拟方法都有默认的实现,所以唯一剩下去做纯虚拟的方法是析构函数。
在派生类中重新实现一个析构函数根本没什么大不了的 – 我总是在我的派生类中重新实现一个析构函数,不pipe是虚拟的还是非虚拟的。
在C ++ 11中,您可以轻松避免inheritance:
struct Interface { explicit Interface(SomeType& other) : foo([=](){ return other.my_foo(); }), bar([=](){ return other.my_bar(); }), /*...*/ {} explicit Interface(SomeOtherType& other) : foo([=](){ return other.some_foo(); }), bar([=](){ return other.some_bar(); }), /*...*/ {} // you can add more types here... // or use a generic constructor: template<class T> explicit Interface(T& other) : foo([=](){ return other.foo(); }), bar([=](){ return other.bar(); }), /*...*/ {} const std::function<void(std::string)> foo; const std::function<void(std::string)> bar; // ... };
在这种情况下,接口具有引用语义,也就是说,你必须确保对象超出接口(也可以使接口具有值语义)。
这些types的接口有其优点和缺点:
- 它们比基于inheritance的多态性需要更多的内存 。
- 它们通常比基于inheritance的多态性更快 。
- 在那些你知道最终types的情况下, 它们要快得多! (像gcc和clang这样的编译器在不具有虚函数types的types中执行更多的优化)。
最后,inheritance是复杂软件devise中万恶之源。 在肖恩家长的价值语义和基于概念的多态性 (强烈build议,这种技术的更好的版本在那里解释),研究以下案件:
假设我有一个应用程序,使用MyShape
接口以多形态处理我的形状:
struct MyShape { virtual void my_draw() = 0; }; struct Circle : MyShape { void my_draw() { /* ... */ } }; // more shapes: eg triangle
在您的应用程序中,您可以使用YourShape
界面对不同的形状进行相同的操作:
struct YourShape { virtual void your_draw() = 0; }; struct Square : YourShape { void your_draw() { /* ... */ } }; /// some more shapes here...
现在说你想使用我在你的应用程序中开发的一些形状。 从概念上讲,我们的形状具有相同的界面,但为了使我的形状在您的应用程序中工作,您将需要扩展我的形状,如下所示:
struct Circle : MyShape, YourShape { void my_draw() { /*stays the same*/ }; void your_draw() { my_draw(); } };
首先,修改我的形状可能根本不可能。 此外,多重inheritance导致通向意大利面代码的道路(设想第三个项目是使用TheirShape
接口,如果他们也调用它们的绘图函数my_draw
,会发生什么?)。
更新:有几个关于基于非inheritance的多态的新引用:
- 肖恩家长的遗产是邪说的基础阶层 。
- 肖恩家长的价值语义和基于概念的多态论谈话。
- Pyry Jahkola的inheritance免费多态性谈话和多图书馆docs 。
- 扎克莱恩的语用types擦除:用优雅的devise模式谈话解决面对面问题 。
- Andrzej的C ++博客 – types擦除部分i , ii , iii和iv 。
- 运行时多态generics编程 – 在ConceptC ++中混合对象和概念
- Boost.TypeErasure文档
- Adobe Poly文档
- Boost.Any , std ::任何提案(修订3) , Boost.Spirit :: hold_any 。
如果您使用的是Microsoft的C ++编译器,那么您可以执行以下操作:
struct __declspec(novtable) IFoo { virtual void Bar() = 0; }; class Child : public IFoo { public: virtual void Bar() override { /* Do Something */ } }
我喜欢这种方法,因为它导致了更小的接口代码,并且生成的代码尺寸可以更小。 novtable的使用将删除对该类中的vtable指针的所有引用,所以您不能直接实例化它。 看到这里的文档 – novtable 。
除了那里写的东西外:
首先,确保你的析构函数也是纯虚拟的
其次,当你执行的时候你可能想要inheritance(而不是通常),只是为了好的措施。
您还可以考虑使用NVI(非虚拟接口模式)实施的合同类别。 例如:
struct Contract1 : boost::noncopyable { virtual ~Contract1(); void f(Parameters p) { assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure"); // + class invariants. do_f(p); // Check post-conditions + class invariants. } private: virtual void do_f(Parameters p) = 0; }; ... class Concrete : public Contract1, public Contract2 { private: virtual void do_f(Parameters p); // From contract 1. virtual void do_g(Parameters p); // From contract 2. };
我仍然是新的C + +开发。 我从Visual Studio(VS)开始。
但是,似乎没有人提到VS (.NET)中的__interface
。 我不太确定这是否是一种声明界面的好方法。 但似乎提供了额外的执法 (在文件中提到)。 这样你就不必显式地指定virtual TYPE Method() = 0;
,因为它会自动转换。
__interface IMyInterface { HRESULT CommitX(); HRESULT get_X(BSTR* pbstrName); };
但是,我不使用它,因为我担心跨平台编译兼容性,因为它只能在.NET下使用。
如果有人有什么有趣的事情,请分享。 🙂
谢谢。
虽然virtual
是事实上定义接口的标准,但是我们不要忘记C ++中带有构造函数的传统C类模式:
struct IButton { void (*click)(); // might be std::function(void()) if you prefer IButton( void (*click_)() ) : click(click_) { } }; // call as: // (button.*click)();
这样做的好处是您可以重新绑定事件运行时,而无需再次构造类(因为C ++没有更改多态types的语法,这是变色龙类的解决方法)。
提示:
- 你可以inheritance它作为一个基类(虚拟和非虚拟都允许),并填写
click
你的后代的构造函数。 - 您可能将函数指针作为
protected
成员,并具有public
引用和/或getter。 - 如上所述,这允许您在运行时切换实现。 因此,这也是一种pipe理状态的方法。 取决于代码中的状态和状态变化的数量,这可能比
switch()
es更快,或者if
s(if
s为3-4左右,if
周期性转换,但总是先测量。 - 如果通过函数指针select
std::function<>
,则可能能够pipe理IBase
所有对象数据。 从这一点,你可以有IBase
价值原理图(例如,std::vector<IBase>
将会工作)。 请注意,这可能会更慢取决于您的编译器和STL代码; 当然,与函数指针甚至虚函数相比,std::function<>
当前实现往往会有开销(这在将来可能会改变)。
这是c ++标准中abstract class
的定义
n4687
13.4.2
抽象类是一个只能用作其他类的基类的类; 除了作为派生类的子对象之外,不能创build抽象类的对象。 如果一个类至less有一个纯虚函数,那么这个类是抽象的。
class Shape { public: // pure virtual function providing interface framework. virtual int getArea() = 0; void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } protected: int width; int height; }; class Rectangle: public Shape { public: int getArea() { return (width * height); } }; class Triangle: public Shape { public: int getArea() { return (width * height)/2; } }; int main(void) { Rectangle Rect; Triangle Tri; Rect.setWidth(5); Rect.setHeight(7); cout << "Rectangle area: " << Rect.getArea() << endl; Tri.setWidth(5); Tri.setHeight(7); cout << "Triangle area: " << Tri.getArea() << endl; return 0; }
结果:矩形区域:35三angular形区域:17
我们已经看到一个抽象类如何定义一个接口的getArea()方法和其他两个类实现相同的function,但不同的algorithm来计算特定的形状区域。