【问题标题】:How can I simplify interface declarations in C++?如何简化 C++ 中的接口声明?
【发布时间】:2010-07-31 23:58:43
【问题描述】:

我的接口声明通常(总是?)遵循相同的方案。这是一个例子:

class Output
{
public:
    virtual ~Output() { }

    virtual void write( const std::vector<char> &data ) = 0;

protected:
    Output() { }

private:
    Output( const Output &rhs ); // intentionally not implemented
    void operator=( const Output &other );  // intentionally not implemented
};

样板总是一样的:公共虚拟析构函数,一些构成实际接口的纯虚拟方法。受保护的默认 ctor,禁用复制构造和复制分配。我开始使用两个小辅助宏,它们可用于将上述内容简化为

ABSTRACT_BASECLASS_BEGIN(Output)
    virtual void write( const std::vector<char> &data ) = 0;
ABSTRACT_BASECLASS_END(Output)

不幸的是,我没有找到一个只用一个宏就能做到这一点的好方法。更好的是,我想完全避免使用宏。然而,我唯一想到的是一个代码生成器,这对我来说有点矫枉过正。

在 C++ 中声明接口的最简单方法是什么 - 直接在语言中。使用预处理器是可以接受的,但我想避免使用外部代码生成器。

【问题讨论】:

  • 使用例如Boost.Preprocessors 序列可以解决这个问题,为什么你会这样做?为什么要避免代码生成(例如通过代码模板等 IDE 支持)?
  • @Georg:这个团队中的大多数开发人员不使用任何 IDE,而是使用 vim/emacs/notepad++ 之类的东西。除此之外,我并不担心编写代码,而是阅读它。对于写作,我可以很容易地破解一个 vim 宏。然而,我们有十几个接口的头文件(每个接口大约有四到五个方法),一遍又一遍地看到相同的四个方法(dtor、ctor、copy ctor、赋值运算符)有点烦人。外部代码生成器(解析 IDL 左右)是可能的,但我想知道没有它们我是否可以逃脱。
  • 真正的问题是为什么?您正在对与接口无关的 hte 继承类施加限制。
  • 我认为没有必要显式声明一个受保护的默认构造函数。由于类是抽象的,所以无论如何也不能直接实例化。
  • @James McNellis:将其设为公共或受保护并不重要,但如果您已将复制构造函数声明为私有,则确实需要另一个用户声明的某种构造函数,否则该类将不会即使作为另一个类的基类也可以实例化。声明复制构造函数的行为将抑制编译器生成的默认构造函数。

标签: c++ interface declaration


【解决方案1】:

就个人而言,我会删除复制构造函数声明。您拥有纯虚函数,因此在任何情况下都无法通过切片创建此类的实例。如果派生类由于其拥有的资源的性质而不可复制;它可以将自己标记为不可复制。

然后我将摆脱受保护的默认构造函数;它什么也不做,在任何情况下你都必须从这个类派生,因为你有纯虚函数,所以它不会阻止任何使用。

虽然标记复制赋值运算符可以防止某人执行*pBase1 = *pBase2(实际上是一个无操作),但我个人不相信尝试阻止它是值得的。由于您的类没有数据成员,因此编译器生成的数据成员没有内在危险,因此用户可以适当地使用指针或对基类的引用。

我会选择:

class Output
{
public:
    virtual ~Output() {}
    virtual void write( const std::vector<char> &data ) = 0;
};

【讨论】:

  • 当然是struct Output { virtual ~Output() {} virtual void write(); }
  • +1:谢谢你的推理。你的回答很有道理。然而,在我们的代码库中有一些可悲的小案例,其中接口并不像我想要的那样纯粹。人们在 START/END 宏之间紧贴成员变量,在少数地方,也有丑小鸭的方法是虚拟的,但不是纯虚拟的。在这些情况下,需要复制构造函数声明(因此需要默认构造函数声明)。
  • 复制赋值运算符是私有的原因是我希望编译器在人们试图运行*pBase1 = *pBase2; 的情况下抱怨。正如您正确指出的那样,这样做应该是无操作的。但是,如果有人试图做类似的事情,这可能表明代码存在缺陷。也许是开发者的大脑故障之类的。因此,与其追求“不要打扰,继续”,我更愿意“让编译器大喊大叫”,以防人们做这样没有任何意义的调用。
【解决方案2】:

考虑使用基类:

class NoncopyableBase
{
public:
    NoncopyableBase() { }
    virtual ~NoncopyableBase() { }
private:
    NoncopyableBase(const NoncopyableBase&);
    void operator=(const NoncopyableBase&);
};

从这个基类派生的任何类都是不可复制的,并且会有一个虚拟析构函数。

【讨论】:

  • 我不知道 gcc,但这段代码 vc10 表示滥用是在派生类上,而不是实际复制的重点。
  • 好答案;我在几个地方看到了这个习语(从你描述的类继承)。我对此唯一的抱怨是,使用继承来节省一些打字工作感觉有点笨拙。毕竟,我没有一个函数 void f( NoncopyableBase *o ) 期望以多态方式访问 NoncopyableBase 对象。
【解决方案3】:

不幸的是,我没有找到一个只用一个宏就能做到这一点的好方法。

IMO,最简单的解决方案是将默认方法放入宏中。像这样:

#include <vector>

#define DEFAULT_CLASS_METHODS(C) public: \
        virtual ~C(){}; \
    protected: \
        C(){}; \
    private: \
        inline C(const C& rhs){}; \
        inline void operator=(const C& other){}; 

class Output{
    DEFAULT_CLASS_METHODS(Output)
public:
    virtual void write(const std::vector<char> &data) = 0;
};

这样,*.h 文件中的每个类定义只需要一个宏。您将需要额外的宏来在某些 *.cpp 文件中实际声明复制构造函数和赋值运算符,或者您可以将默认的复制构造函数和赋值运算符内联,这会将所有内容包装到单个宏中。

唯一的问题是您需要输入两次类名。

有一种不那么优雅的方法来做到这一点,而无需输入两次类名:

#include <vector>

#define BEGIN_INTERFACE(C) class C{ \
    public: \
        virtual ~C(){}; \
    protected: \
        C(){}; \
    private: \
        inline C(const C& rhs){}; \
        inline void operator=(const C& other){}; 

BEGIN_INTERFACE(Output)
public:
    virtual void write(const std::vector<char> &data) = 0;
};

如您所见,在这种情况下,宏吃开 { 括号,这将非常具有误导性。

【讨论】:

  • +1:我喜欢第一个版本!如果不是查尔斯的回答(我必须考虑一下),我可能会接受这个答案。第二个版本确实更紧凑一些,但不幸的是,缺少的{ 让 vim 过于混乱,以至于我无法接受。 :-}
【解决方案4】:

这样的事情怎么样:

ABSTRACT_BASECLASS(Output,
    (virtual void write( const std::vector<char> &data ) = 0;)
    (more methods))

(first)(second)(...) 是一个 boost 预处理器序列,就您的宏而言,它实际上是单个参数:http://www.boost.org/doc/libs/1_43_0/libs/preprocessor/doc/index.html

#define BASECLASS(name, methods) \
...\
BOOST_PP_SEQ_CAT(methods)\  //will concatenate methods declarations
...\

【讨论】:

  • 好的,现在那个看起来像lisp了。
  • +1:这不会赢得选美比赛,但感谢您尝试直接回答我的问题,而不是尝试改写问题。 ;-)
猜你喜欢
  • 2010-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-22
  • 2018-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多