在何处记录C++接口

Where to document interfaces in C++?

本文关键字:接口 C++ 记录 在何处      更新时间:2023-10-16

我有更多的Java背景,因此让我用Java的例子来说明。假设存在以下代码:

interface iFoo {
    /* Do foo */
    void foo();
    /* Do bar */
    void bar();
}
class A implements iFoo {
    void foo() {};
    void bar() {};
}
class B<iFoo> {
    iFoo foo;
    B() {
        foo.foo();
        foo.bar(); 
    } 
} 
//somewhere in the code:
B b = new B<A>();

现在,如果我想实现一个可以用作 B 类型参数的类 C,我知道 C 必须实现 iFoo。因此,我去那里,按照合同设计惯例,所有必要的文档都将在那里(我需要实现哪些方法,那里有什么签名和内联文档。

在C++中,它看起来像这样(如果我错了,请纠正我):

class A {
public:
     void foo();
     void bar();
}
template<class T>
class B {
public:
    T foo;
    B() {
        foo.foo();
        foo.bar(); 
    } 
} 
//somewhere in the code:
B *b = new B<A>();

记录 B 对 T 的期望的最佳位置在哪里?或者反过来,如果我有 A 和 B 并且想要实现一个作为类型参数传递给 B 的类 C,我如何找出 B 对 T 的期望?上面当然是一个非常微不足道的例子,想象一下更大更复杂的类。

在C++中,纯抽象接口类看起来像这样:

struct IFoo
{
    virtual void foo() = 0;
    virtual void bar() = 0;
    virtual ~IFoo() {}
};

然后你像普通类一样继承它

class A : public IFoo
{
public:
    void foo();
    void bar();
};

我会说文档的一个好地方是定义类的头文件。库的用户将查看头文件以查看界面,这是她应该找到文档的位置,无论是整个类或模板还是公共成员。

整个类或模板以及每个公共成员都应该有详细说明目的、假设、先决条件等的文档。

例:

傅炯:

// The "Foo" template implements a gizmo according to Smith, Jones et al.
// A Foo<T> can be used as a drop-in replacement for the usual Zip<T>.
//
// The template parameter T must be a complete object type which is
// assignable and copyable.
template <typename T>
class Foo
{
public:
    // Reflect the template parameters
    using type = T;
    using refence = T &;
    // Constructs a Foo with a given state of being active, a given age
    // and an identifier that represents its glurgh.
    Foo(bool active, int age, char id);
    // Perform the magic act of foo.
    // It is safe to call this function multiple times concurrently.
    // Returns the number of bars.
    //
    // Intended usage:
    //
    //    Foo x(false, 10, 'a');
    //    registerGlobally(x);
    //    x.activate();
    //    return x.perform();
    //
    int perform();
private:
    // ...
};

理想情况下,如果没有其他参考、手册或教程,用户应该能够通过查看标题来了解如何使用该类。如果你有纪律并并行维护一个适当的参考手册,你可以不用这些例子,但必要的信息(参数的含义和要求,返回值的意义)应该在那里。

当您从抽象基类公开继承时,这当然意味着基类已经包含所有接口文档,并且您不需要重复常规信息。但是,您仍然应该留下简短的评论,其中包含具体的覆盖,说明它们对您的实现具体作用。


如果希望模板参数满足某些约束(例如从某个基类派生),则可以通过使用类型特征、SFINAE 和模板元编程以编程方式强制执行。您仍应以人类可读的形式记录模板参数的要求,但在代码中强制实施此类约束是一种很好的卫生措施。

您可以使用虚拟方法执行与C++中的接口相同的操作,将=0放在方法定义的末尾,您就拥有了有效的接口。(它实际上是一个基类,你必须从中派生,因为它本身不能实例化,并且在C++术语中被称为"抽象基类"。

但这给了你答案,没有什么能阻止你说 A 是基本接口,并将其用作"B 对 T 的期望"的定义。因此,如果 A 有 2 个方法,foo() 和 bar(),那么这就是它提供的,这也是从 A 派生的所有类也将具有的。

你可以

像Joachim建议的那样使用接口,但不幸的是,C++没有办法像在Java中那样强制执行它。因此,您必须将其记录为注释,例如在函数上方或类似的地方。