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


当前回答

C++中没有“接口”本身的概念。AFAIK,接口首先在Java中引入,以解决缺少多重继承的问题。事实证明,这个概念非常有用,在C++中使用抽象基类也可以达到同样的效果。

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

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

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

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

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

考虑这一点的一个好方法是继承接口而不是继承实现。在C++中,您可以同时继承接口和实现(公共继承),也可以只继承实现(私有继承)。在Java中,您可以选择只继承接口,而不继承实现。

其他回答

虽然虚拟是定义接口的事实标准,但我们不要忘记经典的类C模式,它在C++中带有构造函数:

struct IButton
{
    void (*click)(); // might be std::function(void()) if you prefer

    IButton( void (*click_)() )
    : click(click_)
    {
    }
};

// call as:
// (button.*click)();

这样做的优点是,您可以在运行时重新绑定事件,而无需再次构造类(因为C++没有用于更改多态类型的语法,这是变色龙类的一种变通方法)。

提示:

您可以将其作为基类继承(允许虚拟和非虚拟),并在后代的构造函数中填充单击。您可以将函数指针作为受保护的成员,并具有公共引用和/或getter。如上所述,这允许您在运行时切换实现。因此,这也是一种管理状态的方法。根据代码中ifs与状态变化的数量,这可能比switch()es或ifs更快(预计周转时间在3-4个ifs左右,但始终要先测量。如果在函数指针上选择std::function<>,则可能能够管理IBase中的所有对象数据。从这一点开始,您可以获得IBase的值示意图(例如,std::vector<IBase>将起作用)。注意,根据编译器和STL代码的不同,这可能会更慢;此外,与函数指针甚至虚拟函数相比,std::function<>的当前实现往往会有开销(这在将来可能会改变)。

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

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

和其他答案一样:

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

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

困惑的


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

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

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

你想这样做的主要原因是,如果你想提供接口方法,就像我所说的那样,但让重写它们成为可选的。

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

在派生类中重新实现析构函数根本没什么大不了的——我总是在派生类里重新实现一个析构函数,不管是虚拟的还是非虚拟的。

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

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

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

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

就我所能测试的而言,添加虚拟析构函数非常重要。我使用的是用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()。