在 C++11 中声明接口的最佳方式

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14323595/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 18:13:51  来源:igfitidea点击:

Best way to declare an interface in C++11

c++interfacec++11polymorphismabstract

提问by emesx

As we all know, some languages have the notion of interfaces. This is Java:

众所周知,有些语言有接口的概念。这是Java:

public interface Testable {
  void test();
}

How can I achieve this in C++ (or C++11) in most compact way and with little code noise? I'd appreciate a solution that wouldn't need a separate definition (let the header be sufficient). This is a very simple approach that even I find buggy ;-)

如何在 C++(或 C++11)中以最紧凑的方式实现这一点,并且几乎没有代码噪音?我很欣赏一个不需要单独定义的解决方案(让标题就足够了)。这是一种非常简单的方法,即使我也觉得有问题 ;-)

class Testable {
public:
  virtual void test() = 0;
protected:
  Testable();
  Testable(const Testable& that);
  Testable& operator= (const Testable& that);
  virtual ~Testable();
}

This is only the beginning.. and already longer that I'd want. How to improve it? Perhaps there is a base class somewhere in the std namespace made just for this?

这只是开始......而且已经比我想要的更长了。如何改进它?也许在 std 命名空间中的某个地方有一个专门为此创建的基类?

采纳答案by Mark B

What about:

关于什么:

class Testable
{
public:
    virtual ~Testable() { }
    virtual void test() = 0;
}

In C++ this makes no implications about copyability of child classes. All this says is that the child must implement test(which is exactly what you want for an interface). You can't instantiate this class so you don't have to worry about any implicit constructors as they can't ever be called directly as the parent interface type.

在 C++ 中,这对子类的可复制性没有影响。所有这一切都表明孩子必须实现test(这正是您想要的接口)。您无法实例化此类,因此您不必担心任何隐式构造函数,因为它们永远无法作为父接口类型直接调用。

If you wish to enforce that child classes implement a destructor you can make that pure as well (but you still have to implement it in the interface).

如果您希望强制子类实现析构函数,您也可以将其设为纯析构函数(但您仍然必须在接口中实现它)。

Also note that if you don't need polymorphic destruction you can choose to make your destructor protected non-virtual instead.

另请注意,如果您不需要多态销毁,您可以选择将您的析构函数保护为非虚拟的。

回答by TemplateRex

For dynamic (runtime) polymorphism, I would recommend using the Non-Virtual-Interface(NVI) idiom. This pattern keeps the interface non-virtual and public, the destructor virtual and public, and the implementation pure virtual and private

对于动态(运行时)多态性,我建议使用非虚拟接口(NVI) 习惯用法。这种模式使接口保持非虚拟和公共,析构函数保持虚拟和公共,实现纯虚拟和私有

class DynamicInterface
{
public:
    // non-virtual interface
    void fun() { do_fun(); } // equivalent to "this->do_fun()"

    // enable deletion of a Derived* through a Base*
    virtual ~DynamicInterface() = default;    
private:
    // pure virtual implementation
    virtual void do_fun() = 0; 
};

class DynamicImplementation
:
    public DynamicInterface
{
private:
    virtual void do_fun() { /* implementation here */ }
};

The nice thing about dynamic polymorphism is that you can -at runtime- pass any derived class where a pointer or reference to the interface base class is expected. The runtime system will automatically downcast the thispointer from its static base type to its dynamic derived type and call the corresponding implementation (typically happens through tables with pointers to virtual functions).

动态多态的好处在于,您可以在运行时传递任何需要指向接口基类的指针或引用的派生类。运行时系统会自动将this指针从其静态基类型向下转换为其动态派生类型并调用相应的实现(通常通过带有指向虚函数的指针的表发生)。

For static (compile-time polymorphism), I would recommend using the Curiously Recurring Template Pattern(CRTP). This is considerably more involved because the automatic down-casting from base to derived of dynamic polymporphism has to be done with static_cast. This static casting can be defined in a helper class that each static interface derives from

对于静态(编译时多态),我建议使用Curiously Recurring Template Pattern(CRTP)。这要复杂得多,因为从动态多态性的基础到派生的自动向下转换必须使用static_cast. 这种静态转换可以在每个静态接口派生自的辅助类中定义

template<typename Derived>
class enable_down_cast
{
private:  
        typedef enable_down_cast Base;    
public:
        Derived const* self() const
        {
                // casting "down" the inheritance hierarchy
                return static_cast<Derived const*>(this);
        }

        Derived* self()
        {
                return static_cast<Derived*>(this);
        }    
protected:
        // disable deletion of Derived* through Base*
        // enable deletion of Base* through Derived*
        ~enable_down_cast() = default; // C++11 only, use ~enable_down_cast() {} in C++98
};

Then you define a static interface like this:

然后你定义一个这样的静态接口:

template<typename Impl>
class StaticInterface
:
    // enable static polymorphism
    public enable_down_cast< Impl >
{
private:
    // dependent name now in scope
    using enable_down_cast< Impl >::self;    
public:
    // interface
    void fun() { self()->do_fun(); }    
protected:
    // disable deletion of Derived* through Base*
    // enable deletion of Base* through Derived*
    ~StaticInterface() = default; // C++11 only, use ~IFooInterface() {} in C++98/03
};

and finally you make an implementation that derives from the interface with itself as parameter

最后你做一个从接口派生的实现,它自己作为参数

class StaticImplementation
:
    public StaticInterface< StaticImplementation > 
{
private:
    // implementation
    friend class StaticInterface< StaticImplementation > ;
    void do_fun() { /* your implementation here */ }
};

This still allows you to have multiple implementations of the same interface, but you need to know at compile-time which implementation you are calling.

这仍然允许您拥有同一接口的多个实现,但您需要在编译时知道您正在调用哪个实现。

So when to use which form?Both forms will let you re-use a common interface and inject pre/post condition testing inside the interface class. The advantage of dynamic polymorphism is that you have runtime flexibility, but you pay for that in virtual function calls (typically a call through a function pointer, with little opportunity for inlining). Static polymporhism is the mirror of that: no virtual function call overhead, but the disadvantage is that you need more boilerplate code and you need to know what you are calling at compile-time. Basically an efficiency/flexiblity tradeoff.

那么什么时候使用哪种形式呢?这两种形式都可以让您重用通用接口并在接口类中注入前/后条件测试。动态多态的优势在于您具有运行时灵活性,但您需要在虚函数调用中为此付出代价(通常是通过函数指针进行调用,几乎没有内联机会)。静态多态反映了这一点:没有虚函数调用开销,但缺点是您需要更多样板代码,并且需要知道在编译时调用的是什么。基本上是效率/灵活性的权衡。

NOTE:for compile-time polymporhism, you can also use template parameters. The difference between static interface through the CRTP idiom and ordinary template parameters is that CRTP-type interface are explicit (based on member functions), and template interface are implicit (based on valid expressions)

注意:对于编译时多态性,您还可以使用模板参数。通过 CRTP 惯用语的静态接口与普通模板参数的区别在于 CRTP 类型的接口是显式的(基于成员函数),模板接口是隐式的(基于有效表达式)

回答by Aahzbg

According to Scott Meyers (Effective Modern C++): When declaring interface (or polymorphic base class) you need virtual destructor, for proper results of operations like deleteor typeidon a derived class object accessed through a base class pointer or reference.

根据 Scott Meyers(Effective Modern C++)的说法:在声明接口(或多态基类)时,您需要虚拟析构函数,以获得通过基类指针或引用访问的派生类对象之类的操作deletetypeid对派生类对象的正确结果。

virtual ~Testable() = default;

However, a user-declared destructor suppresses generation of the move operations, so to support move operations you need to add:

但是,用户声明的析构函数会抑制移动操作的生​​成,因此要支持移动操作,您需要添加:

Testable(Testable&&) = default; 
Testable& operator=(Testable&&) = default;

Declaring the move operations disables copy operations and you need also:

声明移动操作会禁用复制操作,您还需要:

Testable(const Testable&) = default;
Testable& operator=(const Testable&) = default;

And the final result is:

最后的结果是:

class Testable 
{
public:
    virtual ~Testable() = default; // make dtor virtual
    Testable(Testable&&) = default;  // support moving
    Testable& operator=(Testable&&) = default;
    Testable(const Testable&) = default; // support copying
    Testable& operator=(const Testable&) = default;

    virtual void test() = 0;

};

Another interesting article here: The Rule of Zero in C++

另一篇有趣的文章:C++ 中的零规则

回答by Mark Ransom

By replacing the word classwith struct, all of the methods will be public by default and you can save a line.

通过更换字classstruct,所有的方法将默认为公开,你可以节省线。

There's no need to make the constructor protected, since you can't instantiate a class with pure virtual methods anyway. This goes for the copy constructor as well. The compiler-generated default constructor will be empty since you don't have any data members, and is completely sufficient for your derived classes.

没有必要保护构造函数,因为无论如何你都不能用纯虚方法实例化一个类。这也适用于复制构造函数。编译器生成的默认构造函数将为空,因为您没有任何数据成员,并且对于您的派生类完全足够。

You're right to be concerned about the =operator since the compiler-generated one will certainly do the wrong thing. In practice nobody ever worries about it because copying one interface object to another never makes sense; it's not a mistake that happens commonly.

您担心=运算符是正确的,因为编译器生成的运算符肯定会做错事。在实践中,没有人会担心它,因为将一个接口对象复制到另一个永远没有意义;这不是经常发生的错误。

Destructors for an inheritable class should alwaysbe either public and virtual, or protected and non-virtual. I prefer public and virtual in this case.

可继承类的析构函数应该始终是公共和虚拟的,或者是受保护的和非虚拟的。在这种情况下,我更喜欢公共和虚拟。

The final result is only one line longer than the Java equivalent:

最终结果仅比 Java 等价物长一行:

struct Testable {
    virtual void test() = 0;
    virtual ~Testable();
};

回答by Jason

Keep in mind that the "rule of three" is unnecessary if you aren't managing pointers, handles, and/or all the data-members of the class have their own destructors that will manage any clean-up. Also in the case of a virtual base class, because the base class can never be directly instantiated, it's not necessary to declare a constructor if all you're wanting to-do is define an interface that has no data members ... the compiler defaults are just fine. The only item you would need to keep is the virtual destructor if you are planning on calling deleteon a pointer of the interface type. So in reality your interface can be as simple as:

请记住,如果您不管理指针、句柄和/或类的所有数据成员都有自己的析构函数来管理任何清理,则“三规则”是不必要的。同样在虚拟基类的情况下,因为基类永远不能直接实例化,如果您想要做的只是定义一个没有数据成员的接口,则没有必要声明构造函数......编译器默认值就好了。如果您计划调用delete接口类型的指针,则唯一需要保留的项目是虚拟析构函数。所以实际上你的界面可以很简单:

class Testable 
{
    public:
        virtual void test() = 0;  
        virtual ~Testable();
}