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


当前回答

在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。

其他回答

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

困惑的


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

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

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

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

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

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

您还可以考虑使用NVI(非虚拟接口模式)实现的合约类。例如:

struct Contract1 : boost::noncopyable
{
    virtual ~Contract1() = default;
    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:
    void do_f(Parameters p) override; // From contract 1.
    void do_g(Parameters p) override; // From contract 2.
};

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

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

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

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

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

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

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