Лучшая практика для определения интерфейса времени компиляции - PullRequest
2 голосов
/ 26 января 2012

Допустим, у меня есть следующий устаревший код, который я могу улучшить:

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_B или HAS_FEATURE_C (против связанного кода, просто фактически не используемого из-за проверок во время выполнения) ).

Несколько заметок

  1. Флаги функций не являются взаимоисключающими. Можно определить любую их комбинацию.

  2. Как я понимаю, определение подкласса для каждой функции и затем использование множественного наследования для объединения требуемых интерфейсов недостаточно. Помните, что производный класс должен иметь возможность реализовывать любую комбинацию функций во время компиляции и не включать никаких функций, которые не определены для этой компиляции.

  3. Политическая стоимость изменения кода означает, что я должен быть в состоянии избавиться от всех директив или не делать это вообще. Другими словами, недостаточно просто переместить определения #ifdef, чтобы сделать базовый класс красивее.

  4. Существует достаточно комбинаций функций, поэтому любой метод грубой силы был бы нереалистичным. Я не собираюсь делать тысячи подклассов.

  5. Я не знаю шаблонов, но я хочу узнать, является ли это ключом.

Ответы [ 3 ]

2 голосов
/ 26 января 2012

Есть способы. Однако действительно ли они полезны?

Многие библиотеки используют условную компиляцию #if для предложения дополнительных функций подписки. Это давняя практика.

Вы можете «покончить» с этим, используя дополнительные модули, но не уверены, что в результате код станет более читабельным. Напротив, люди, использующие ваш код, теперь должны понимать, что происходит, когда это было просто очевидно раньше.

С другой стороны, поскольку мы говорим об интерфейсах, вы можете просто захотеть разделить интерфейс на несколько компонентов (если это возможно), каждый из которых посвящен ряду функций. Знание, можете ли вы или нет, потребовало бы больше знаний о тонкостях класса ... но для меня это слишком похоже на объект Бога.

1 голос
/ 26 января 2012

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

Предполагается, что ваши функциональные флаги в основном являются логическими значениями, поэтому HAS_FEATURE_A равно 0, если функция не включена. Если это не так, вы можете сделать параметр шаблона типа int и специализироваться на 1 вместо true. Если неиспользуемые флаги функций не определены, вы не сможете полностью избавиться от использования #if или #ifdef, поскольку это единственный способ принимать решения, основываясь на том, определен макрос или нет.

1 голос
/ 26 января 2012

Я бы определенно избегал использования препроцессора в этом случае.

Вы могли бы разделить ваши функции на отдельные интерфейсы, и ваш производный класс мог бы выбирать и точно выбирать, какие функции (и, следовательно, какие интерфейсы) он будет реализовывать.

Тогда вам может понадобиться другой интерфейс поиска для доступа к функциям:

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 #, которые не поддерживают множественное наследование на уровне языка, позволяют вам делать это.

...