dolphy
dolphy

Reputation: 6518

Best practice for compile time interface definition

Let's say I have the following legacy code that I have the opportunity to improve:

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;
};

Is there a way to do away with the #ifdef method of defining the interface at compile time without increasing the runtime size of the new design? In other words, I need to retain the fact that a derived class which compiles with only HAS_FEATURE_A will not contain the code for HAS_FEATURE_B or HAS_FEATURE_C (vs the code being linked in, just not actually used because of runtime checks).

A few notes

  1. The feature flags are not mutually exclusive. Any combination of them can be defined.

  2. As I perceive it, defining a subclass for each feature and then using multiple inheritance to bring the desired interfaces together is not sufficient. Remember that the derived class must be able to implement any combination of the features at compile time, and include no feature which is not defined for that compile.

  3. The political cost of changing the code means that I must be able to get rid of all directives, or not do it at all. In other words, it is not enough to simply move the #ifdef definitions around to make the base class prettier.

  4. There are enough combinations of features that any brute force approach would be unrealistic. I'm not going to make thousands of subclasses.

  5. I don't know templates, but I'm willing to learn if that's the key.

Upvotes: 2

Views: 1272

Answers (3)

Matthieu M.
Matthieu M.

Reputation: 300249

There are ways. However are they really useful ?

Many libraries use the #if conditional compilation to propose additional opt-in features. It's a long established practice.

You may be able to "do away" with this using mix-ins, but it is not sure that the code will become more readable as a result. On the contrary people using your code will now have to understand what's going on when it was just obvious before.

On the other hand, since we are speaking about interfaces, you might simple want to split the interface into several components (if possible), each dedicated to a number of features. Knowing whether you can or not would require more knowledge about the intricacies of the class... but it looks too much like a God Object to me.

Upvotes: 2

Grizzly
Grizzly

Reputation: 20211

The fpllowing assumes you need to keep the BaseInterfaceClass instead of changing the inheritance of the classes using it (otherwise simply defining an interface for each feature and inheriting only those you need is a possibility). You could use multiple inheritance in conjunction with specialized templates to get only those interface parts you need:

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*/};

If you don't want to use multiple inheritance (since you don't want to make the object bigger), you can unroll it into one inheritance chain:

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*/};

This assumes that your featureflags are basically a bool, so HAS_FEATURE_A is 0 if the feature is not enabled. If that is not the case you might make the templateparameter type int and specialize for 1 instead of true. If unused feature flags are undefined you can't completely get rid of using #if or #ifdef, since that is the only way to make decisions based on whether or not a macro is defined.

Upvotes: 1

DXM
DXM

Reputation: 4543

I would definitely avoid using preprocessor in this case.

You could separate your features into distinct interfaces and have your deriving class pick and choose exactly which features (and therefore which interfaces) it will implement.

Then you might need to have another lookup interface to get to the features:

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;
};

In this case, your feature interfaces would have to derive from something common.

Yes this solution will use multiple inheritance (i.e. your concrete component will derive from IFeatureXX interfaces as well as IFullComponent) but even for people who avoid multiple inheritance because of perceived extra-complexity, remember that you are only inheriting interfaces, not actual implementation. Even java and C#, which don't support multiple inheritance at a language level, allow you to do this.

Upvotes: 1

Related Questions