Ищете лучший подход к условному наследованию в C

#c #inheritance

Вопрос:

 void C :: setmethodForC(...)
{
   B::setmethod(...);
}

class C: public A, public B
{

   public:

    void setmethodForC();
    .....

}
 

В приведенном выше коде A и B-это две службы, которые использует класс C. Теперь этот код действителен для нескольких целей, и все цели не поддерживают службу B. Поэтому мне понадобилось условное наследование, где служба B включена только для определенных целей. Для этого я внес следующие изменения, чтобы отключить использование службы B в C.

 #ifdef B_SUPPORTED
void C :: setmethodForC(...)
{
   B::setmethod(...);
}
#endif

class C: public A
#ifdef B_SUPPORTED
, public B
#endif
{

   public:
#ifdef B_SUPPORTED
    void setmethodForC();
    .....
#endif

}
 

Поскольку это хорошо скомпилировано, но есть ли лучший способ сделать это?

Комментарии:

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

2. @Moia отредактировал вопрос для получения более подробной информации.

3. Есть ли у вас какие-либо причины добавлять новые методы вместо того, чтобы оставлять их пустыми, если отсутствует функциональность?

4. Это классический пример проблемы XY . Пожалуйста, объясните, ЗАЧЕМ вам это нужно, так как я уверен, что есть лучший и более простой способ решить вашу реальную проблему. Можно сделать то, что вы просили, но это странно в данном контексте, и это квалифицируется как продвинутое решение (я не хочу запутывать ваш разум).

Ответ №1:

В вашем вопросе неясно, почему здесь необходимо наследование, вы можете достичь желаемого результата, используя композицию и шаблон нулевого объекта https://en.wikipedia.org/wiki/Null_object_pattern, вот пример:

 #include <iostream>
#include <memory>

struct OptionalService
{
    virtual void someMethod() = 0;
    virtual ~OptionalService() {}
};

struct DoSomethingService final : OptionalService
{
    void someMethod() override { std::cout << "Do Something" << std::endl; }
};

struct NoActionService final : OptionalService
{
    void someMethod() override {  }
};

class MyClass
{
    std::unique_ptr<OptionalService> _service;
public:
    explicit MyClass(std::unique_ptr<OptionalService> service) : _service(std::move(service)) {}
    void someMethod( ) { _service->someMethod(); }
};

int main() {
    MyClass noAction(std::make_unique<NoActionService>());

    noAction.someMethod();

    MyClass someAction(std::make_unique<DoSomethingService>());

    someAction.someMethod();

    return 0;
}
 

Что-то подобное можно получить, используя статический полиморфизм ase хорошо, это избавит вас от класса интерфейса:

 template<typename Service>
class MyClassB
{
    Service _service;
public:
    explicit MyClassB(const Serviceamp; s = Service()) : _service(s) {}
    void someMethod() { _service.someMethod(); }
};

typedef MyClassB<NoActionService> MyClassBNoAction;
typedef MyClassB<DoSomethingService> MyClassBSomething;
int main() {
    MyClassBNoAction noAction;

    noAction.someMethod();

    MyClassBSomething someAction;

    someAction.someMethod();

    return 0;
}
 

Комментарии:

1. Невозможно сказать, действительно ли это нужно ОП. В любом случае вам не нужно использовать std::unique_ptr здесь. Передача службы по ссылке предотвратит обнуление и не приведет к принудительному использованию кучи.

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

Ответ №2:

Как уже было сказано, вопрос не очень ясен, и это, вероятно, проблема XY. Сказав это, один из способов справиться с различными целевыми классами-это перенести проблему с определения на создание.

Целевой конкретный класс теперь является дочерним по отношению к общему классу

фу.ч

 class FooBase {};

class FooGeneric : public FooBase {};

class FooTargetSpecificA : public FooGeneric{};

class FooTargetSpecificB : public FooGeneric{};
 

Переместите целевой параметр в переменную времени компиляции. Однако есть и другие способы добиться этого.

цель.h

 #ifdef A_SUPPORTED
constexpr bool HasFeatureA = true;
#else
constexpr bool HasFeatureA = false;
#endif

#ifdef B_SUPPORTED
constexpr bool HasFeatureB = true;
#else
constexpr bool HasFeatureB = false;
#endif
 

Делегируйте построение вашего объекта на фабрику с проверкой времени компиляции для правильного класса.

завод.ч

 #include "foo.h"
#include "target.h"

inline std::unique_ptr<FooBase> createFoo()
{
   if constexpr (HasFeatureA)
      return std::make_unique<FooTargetSpecificA>();
   else if constexpr (HasFeatureB)
      return std::make_unique<FooTargetSpecificB>();
   else
      return std::make_unique<FooGenerc>();
}