如何设置表示接口的类?这只是一个抽象基类吗?


当前回答

我的答案与其他人基本相同,但我认为还有两件重要的事情要做:

如果有人试图删除IDemo类型的对象,请在接口中声明一个虚拟析构函数或创建一个受保护的非虚拟析构器,以避免未定义的行为。使用虚拟继承来避免多重继承的问题。(当我们使用接口时,通常会有多重继承。)

和其他答案一样:

使用纯虚拟方法创建类。通过创建另一个重写这些虚拟方法的类来使用该接口。类IDemo{公众:虚空OverrideMe()=0;virtual~IDemo(){}}或类IDemo{公众:虚空OverrideMe()=0;受保护的:~IDemo(){}}和class Child:虚拟公共IDemo{公众:虚拟空OverrideMe(){//做一些事情}}

其他回答

如果您使用的是Microsoft的C++编译器,则可以执行以下操作:

struct __declspec(novtable) IFoo
{
    virtual void Bar() = 0;
};

class Child : public IFoo
{
public:
    virtual void Bar() override { /* Do Something */ }
}

我喜欢这种方法,因为它会产生更小的接口代码,生成的代码大小也会明显更小。novtable的使用删除了该类中对vtable指针的所有引用,因此您永远不能直接实例化它。请参阅此处的文档-novtable。

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三角形面积:17

我们已经看到了抽象类是如何根据getArea()定义接口的,另外两个类实现了相同的函数,但使用了不同的算法来计算特定于形状的面积。

下面是c++标准中抽象类的定义

第4687页

13.4.2

抽象类是只能用作其他类的基类的类;没有抽象对象类只能作为派生类的子对象来创建。如果类至少具有一个纯虚拟函数。

如果您只需要接口的静态绑定(没有虚拟的,没有接口类型本身的实例,接口仅作为指南):

#include <iostream>
#include <string>

// Static binding interface
// Notice: instantiation of this interface should be usefuless and forbidden.
class IBase {
 protected:
  IBase() = default;
  ~IBase() = default;

 public:
  // Methods that must be implemented by the derived class
  void behaviorA();
  void behaviorB();

  void behaviorC() {
    std::cout << "This is an interface default implementation of bC().\n";
  };
};

class CCom : public IBase {
  std::string name_;

 public:
  void behaviorA() { std::cout << "CCom bA called.\n"; };
};

class CDept : public IBase {
  int ele_;

 public:
  void behaviorB() { std::cout << "CDept bB called.\n"; };
  void behaviorC() {
    // Overwrite the interface default implementation
    std::cout << "CDept bC called.\n";
    IBase::behaviorC();
  };
};

int main(void) {
  // Forbid the instantiation of the interface type itself.
  // GCC error: ‘constexpr IBase::IBase()’ is protected within this context
  // IBase o;

  CCom acom;
  // If you want to use these interface methods, you need to implement them in
  // your derived class. This is controled by the interface definition.
  acom.behaviorA();
  // ld: undefined reference to `IBase::behaviorB()'
  // acom.behaviorB();
  acom.behaviorC();

  CDept adept;
  // adept.behaviorA();
  adept.behaviorB();
  adept.behaviorC();
  // adept.IBase::behaviorC();
}

在C++11中,您可以轻松避免完全继承:

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;
  // ...
};

在这种情况下,接口具有引用语义,即您必须确保对象比接口更长寿(也可以创建具有值语义的接口)。

这些类型的接口有其优点和缺点:

它们比基于继承的多态性需要更多的内存。它们通常比基于继承的多态性更快。在那些你知道最终类型的情况下,它们要快得多!(像gcc和clang这样的一些编译器在没有/继承自具有虚拟函数的类型的类型中执行更多的优化)。

最后,继承是复杂软件设计中所有邪恶的根源。在Sean Parent的《基于价值语义和概念的多态性》(强烈推荐,此处解释了该技术的更好版本)中,研究了以下案例:

假设我有一个应用程序,在其中我使用MyShape界面处理我的形状:

struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. 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(); }
};

首先,修改我的形状可能根本不可能。此外,多重继承导致了意大利面代码的发展(假设第三个项目使用TheirShape接口……如果他们也调用绘图函数my_draw会发生什么?)。

更新:有一些关于非继承多态性的新参考:

Sean Parent的继承权是恶语的基础。Sean Parent的价值语义和基于概念的多态性谈话。Pyry Jahkola的无继承多态性演讲和poly库文档。Zach Laine的实用类型擦除:用优雅的设计模式解决OOP问题。Andrzej的C++博客-类型Erasure第i、ii、iii和iv部分。ConceptC中混合对象和概念的运行时多态泛型编程++Boost.TypeErasure文档Adobe Poly文档Boost.Any,std::任何提案(修订版3),Boost.Spirit::hold_Any。