提问



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

最佳参考


要扩展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
        }
};


你不必为虚拟析构函数包含一个主体 - 事实证明一些编译器在优化空的析构函数时遇到了麻烦,你最好使用默认的析构函数。

其它参考1


使用纯虚方法创建一个类。通过创建覆盖这些虚拟方法的另一个类来使用该接口。


纯虚方法是一种定义为虚拟并分配给0的类方法。


class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Child : public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

其它参考2


除了C#/Java中的抽象基类之外,你有一个特殊的接口类型类的全部原因是因为C#/Java不支持多重继承。


C ++支持多重继承,因此不需要特殊类型。没有非抽象(纯虚拟)方法的抽象基类在功能上等同于C#/Java接口。

其它参考3


C ++中没有接口本身的概念。 AFAIK,接口首先在Java中引入,以解决缺少多重继承问题。这个概念已经证明是非常有用的,并且通过使用抽象基类可以在C ++中实现相同的效果。


抽象基类是一个类,其中至少有一个成员函数(Java lingo中的方法)是使用以下语法声明的纯虚函数:


class A
{
  virtual void foo() = 0;
};


无法实例化抽象基类,即。即您不能声明类A的对象。您只能从A派生类,但任何不提供foo()实现的派生类也将是抽象的。为了不再抽象,派生类必须为它继承的所有纯虚函数提供实现。


请注意,抽象基类不仅仅是一个接口,因为它可以包含非纯虚拟的数据成员和成员函数。接口的等价物是抽象基类,没有任何只有纯虚函数的数据。


而且,正如Mark Ransom指出的那样,抽象基类应该像任何基类一样提供虚拟析构函数。

其它参考4


到目前为止我可以测试,添加虚拟析构函数非常重要。我使用new创建的对象并使用delete销毁。


如果未在接口中添加虚拟析构函数,则不会调用继承类的析构函数。


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()

其它参考5


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



  1. 在您的界面中声明虚拟析构函数或创建受保护的非虚拟析构函数,以避免在有人尝试删除IDemo类型的对象时出现未定义的行为。

  2. 使用虚拟继承来避免多重继承问题。 (当我们使用接口时,通常会有多重继承。)



和其他答案一样:



  • 使用纯虚方法创建一个类。

  • 通过创建覆盖这些虚拟方法的另一个类来使用该接口。


    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
            }
    }
    


其它参考6


上面所有的好答案。
还应该记住一件额外的事情 - 你也可以拥有一个纯粹的虚拟析构函数。唯一的区别是你仍然需要实现它。


困惑?



    --- header file ----
    class foo {
    public:
      foo() {;}
      virtual ~foo() = 0;

      virtual bool overrideMe() {return false;}
    };

    ---- source ----
    foo::~foo()
    {
    }



你想要这样做的主要原因是,如果你想提供接口方法,就像我一样,但是可以覆盖它们。


要使类成为接口类,需要一个纯虚方法,但所有虚方法都有默认实现,因此生成纯虚拟的唯一方法是析构函数。


在派生类中重新实现析构函数根本没什么大不了的 - 我总是在派生类中重新实现析构函数,无论是否为虚函数。

其它参考7


在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的价值语义学和基于概念的多态性中(强烈推荐,在此解释了该技术的更好版本),研究了以下案例:[46] [47] [49]]]


假设我有一个应用程序,我使用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的价值语义和基于概念的多态性谈话。

  • Pyry Jahkola的继承自由多态论和聚库文档。

  • Zach Laine的实用型擦除:用优雅的设计模式解决OOP问题。

  • Andrzej的C ++博客 - 类型Erasure部分i,ii,iii和iv。

  • 运行时多态泛型编程 - 在ConceptC ++中混合对象和概念

  • Boost.TypeErasure docs

  • Adob​​e Poly docs

  • Boost.Any,std ::任何提案(修订版3),Boost.Spirit :: hold_any。


其它参考8


如果您正在使用Microsoft的C ++编译器,那么您可以执行以下操作:[50] [51] [52] [53] [54] [55] [56] [57] [58] [59] [60] [61] [62] [63] [64]


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

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


我喜欢这种方法,因为它会导致更小的接口代码,并且生成的代码大小可以显着缩小。使用novtable删除对该类中vtable指针的所有引用,因此您永远不能直接实例化它。请参阅此处的文档 - novtable。[65]

其它参考9


对那里写的内容有点补充:


首先,确保您的析构函数也是纯虚拟的


其次,您可能希望在实施时虚拟(而不是正常)继承,只是为了获得良好的衡量标准。

其它参考10


您还可以考虑使用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.
};

其它参考11


我仍然是C ++开发的新手。我从Visual Studio(VS)开始。


然而,似乎没有人提到VS (。NET)中的__interface。我非常确定这是否是声明接口的好方法。但它似乎提供了额外的强制执行(在文档中提到)。这样你就不必明确指定virtual TYPE Method() = 0;,因为它会自动转换。[66]


__interface IMyInterface {
   HRESULT CommitX();
   HRESULT get_X(BSTR* pbstrName);
};



  但是,我不使用它,因为我担心跨平台编译兼容性,因为它只能在.NET下使用。



如果有人有任何有趣的事情,请分享。 :-)


谢谢。

其它参考12


这是c ++标准中abstract class的定义


n4687


13.4.2



  抽象类是一个只能用作其他类的基类的类;没有抽象的对象
  除了作为派生自它的类的子对象之外,可以创建类。如果它至少有一个类是抽象的
  一个纯虚函数。


其它参考13


虽然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 ++没有用于更改多态类型的语法,这是变色龙类的变通方法)。


提示:



  • 您可以从此继承为基类(允许虚拟和非虚拟),并在后代的构造函数中填充click

  • 您可能将函数指针作为protected成员并具有public引用和/或getter。

  • 如上所述,这允许您在运行时切换实现。因此,它也是管理状态的一种方式。根据代码中if与状态变化的数量,这个可能比switch() es或[[if s(转折预计在3-4 if s左右,但总是先测量。

  • 如果选择std::function<>而不是函数指针,可能能够在IBase中管理所有对象数据。从这一点开始,您可以获得IBase的值示意图(例如,std::vector<IBase>将起作用)。请注意,根据您的编译器和STL代码,此可能会更慢;另外,std::function<>的当前实现与函数指针甚至虚函数相比往往会产生开销(这可能会在未来发生变化)。


其它参考14


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()定义接口,另外两个类实现相同的函数,但使用不同的算法来计算特定于形状的区域。