编译时接口定义的最佳实践

Best practice for compile time interface definition

本文关键字:最佳 定义 接口 编译      更新时间:2023-10-16

假设我有机会改进以下遗留代码:

class BaseInterfaceClass
{
#if( HAS_FEATURE_A == 1 )
    void doFeatureA1() = 0;
    void doFeatureA2() = 0;
#endif
#if( HAS_FEATURE_B == 1 )
    void doFeatureB1() = 0;
    void doFeatureB2() = 0;
    void doFeatureB3() = 0;
#endif
#if( HAS_FEATURE_C == 1 )
    void doFeatureC1() = 0;
#endif
    void doBaseFeature1() = 0;
    void doBaseFeature2() = 0;
};

有没有一种方法可以在不增加新设计的运行时大小的情况下取消在编译时定义接口的#ifdef方法?换句话说,我需要保留这样一个事实,即仅使用HAS_FEATURE_A编译的派生类将不包含HAS_FEATURE_BHAS_FEATURE_C的代码(而不是链接的代码,只是由于运行时检查而没有实际使用)。

一些注意事项

  1. 功能标志不是互斥的。它们的任何组合都可以定义。

  2. 据我所知,为每个特性定义一个子类,然后使用多重继承将所需的接口组合在一起是不够的。请记住,派生类必须能够在编译时实现功能的任何组合,并且不包含未为该编译定义的功能。

  3. 更改代码的政治成本意味着我必须能够摆脱所有指令,或者根本不这么做。换句话说,仅仅移动#ifdef定义以使基类更漂亮是不够的。

  4. 有足够多的功能组合,任何暴力手段都是不现实的。我不会做成千上万的子类。

  5. 我不知道模板,但如果这是关键,我愿意学习。

有很多方法。然而,它们真的有用吗?

许多库使用#if条件编译来提出附加的选择加入功能。这是一种由来已久的做法。

您可能可以使用mix-ins来"消除"这种情况,但不确定代码是否会因此变得更可读。相反,使用你的代码的人现在必须了解发生了什么,而这在以前是显而易见的。

另一方面,由于我们谈论的是接口,您可能只想将接口拆分为几个组件(如果可能的话),每个组件都专用于许多功能。要知道你能不能,需要更多关于课程复杂性的知识。。。但对我来说,它太像上帝的对象了。

在这种情况下,我肯定会避免使用预处理器。

您可以将特性分离为不同的接口,并让派生类选择它将实现的特性(从而选择接口)。

然后,您可能需要另一个查找界面来访问这些功能:

class IFeature
{
};
class IFeatureA : public IFeature
{
    virtual void DoSomething() = 0;
};
class IFeatureB : public IFeature
{
    virtual void DoSomethingElse() = 0;
};
class IFullComponent
{
    virtual void GetFeature( GUID featureId, IFeature** ppFeature ) = 0;
};

在这种情况下,您的功能接口将不得不从一些常见的东西派生出来。

是的,该解决方案将使用多重继承(即,您的具体组件将从IFeatureXX接口和IFullComponent派生),但即使对于那些因为感知到额外的复杂性而避免多重继承的人来说,也要记住,您只是在继承接口,而不是实际实现。即使是java和C#,它们不支持语言级别的多重继承,也允许您这样做。

fpllowing假设您需要保留BaseInterfaceClass,而不是使用它来更改类的继承(否则,只需为每个功能定义一个接口并只继承您需要的功能是可能的)。您可以将多重继承与专用模板结合使用,只获得您需要的接口部分:

template<bool B> class FeatureABase {};
template<> class FeatureABase<true> {
public:
  virtual void doFeatureA1() = 0;
  virtual void doFeatureA2() = 0;
};
//similar definitions for FeatureBBase and FutureCBase here
class BaseInterfaceClass: public FeatureABase<HAS_FEATURE_A>, public FeatureBBase<HAS_FEATURE_B>, public FeatureCBase<HAS_FEATURE_C>
{/*not flag dependent parts here*/};
//If you want to get rid of the preprocessor flags completely, you could define
//BaseInterfaceClass as a template itself:
template<bool HasA, bool HasB, bool HasC>
class BaseInterfaceClass: public FeatureABase<HasA>, public FeatureBBase<HasB>, public FeatureCBase<HasC>
{/*not flag dependent parts here*/};

如果你不想使用多重继承(因为你不想让对象更大),你可以把它展开成一个继承链:

template<bool B> class FeatureBBase: public FeatureABase<HAS_FEATURE_A> {};
template<> class FeatureBBase<true>: public FeatureABase<HAS_FEATURE_A> {
public:
    virtual void doFeatureB1() = 0;
    virtual void doFeatureB2() = 0;
    virtual void doFeatureB3() = 0;
};
template<bool B> class FeatureCBase: public FeatureBBase<HAS_FEATURE_B> {};
template<> class FeatureCBase<true>: public FeatureBBase<HAS_FEATURE_B> {
public:
    virtual void doFeatureC1() = 0;
}
class BaseInterfaceClass: public FeatureCBase<HAS_FEATURE_C>
{/*not flag dependent parts here*/};

这假设您的featureflags基本上是一个bool,因此如果未启用该功能,则HAS_FEATURE_A0。如果不是这样,您可以使模板参数类型为int,并专门用于1而不是true。如果未定义未使用的特征标志,则不能完全取消使用#if#ifdef,因为这是根据是否定义宏来做出决策的唯一方法。