Определить реализацию класса динамически через конструктор - PullRequest
0 голосов
/ 26 октября 2018

Я хочу создать класс, который ведет себя определенным образом - например, выплевывает определенные значения из функции double getValue(const int& x) const - на основе «типа», который был передан в его конструктор.Прямо сейчас у меня есть два метода:

  1. Сохраните переданный «тип» и затем оценивайте оператор switch в getValue каждый раз, когда он вызывается, чтобы решить, какую реализацию использовать.
  2. Используйте оператор switch для переданного "типа" (в конструкторе), чтобы создать внутренний объект, который представляет желаемую реализацию.Таким образом, switch больше не требуется в самом getValue.

Метод 1 «кажется» неэффективным, так как switch вызывается каждый раз, когда я вызываю getValue.Метод 2 кажется несколько неуклюжим, поскольку мне нужно использовать <memory>, и он также делает копирование / назначение моего класса нетривиальным.

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

Пример кода:

#include <memory>

enum class ImplType { Simple1, Simple2 /* more cases */ };

class MyClass1
{
private:
    const ImplType implType;

public:
    MyClass1(const ImplType& implType) : implType(implType) { }

    double getValue(const int& x) const
    {
        switch (implType)
        {
        case ImplType::Simple1: return 1; /* some implemention */
        case ImplType::Simple2: return 2; /* some implemention */
        }
    }
};

class MyClass2
{
private:
    struct Impl { virtual double getValue(const int& x) const = 0; };
    struct ImplSimple1 : Impl { double getValue(const int& x) const override { return 1; /* some implemention */ } };
    struct ImplSimple2 : Impl { double getValue(const int& x) const override { return 2; /* some implemention */ } };

    const std::unique_ptr<Impl> impl;

public:
    MyClass2(const ImplType& implType) : impl(std::move(createImplPtr(implType))) { }

    static std::unique_ptr<Impl> createImplPtr(const ImplType& implType)
    { 
        switch (implType)
        {
        case ImplType::Simple1: return std::make_unique<ImplSimple1>();
        case ImplType::Simple2: return std::make_unique<ImplSimple2>();
        }
    }

    double getValue(const int& x) const { return impl->getValue(x); }
};


int main()
{
    MyClass1 my1(ImplType::Simple1);
    MyClass2 my2(ImplType::Simple1);

    return 0;
}

Ответы [ 3 ]

0 голосов
/ 26 октября 2018

Если у вас есть закрытый набор типов, из которых вы можете выбрать, вы хотите std::variant:

using MyClass = std::variant<MyClass1, MyClass2, MyClass3, /* ... */>;

Это не использует динамическое распределение - это в основном современная безопасная альтернатива типу union.

0 голосов
/ 26 октября 2018

Более объектно-ориентированный подход:

class Interface
{
    public:
        virtual int getValue() = 0;
};

class GetValueImplementation1 : public Interface
{
    public:
        int getValue() {return 1;}
};

class GetValueImplementation2 : public Interface
{
    public:
        int getValue() {return 2;}
};

class GeneralClass
{
    public:
        GeneralClass(Interface *interface) : interface(interface) {}
        ~GeneralClass() 
        {
            if (interface)
                delete interface;
        }

        int getValue() { return interface->getValue(); }

    private:
        Interface *interface;
};

Итак, в этом случае вы можете использовать его без указателей:

int main()
{
    GeneralClass obj1(new GetValueImplementation1());
    GeneralClass obj2(new GetValueImplementation2());

    cout << obj1.getValue() << " " << obj2.getValue();
    return 0;
}

Вывод будет:

1 2

Но в этом случае вы должны быть осторожны с нулевыми указателями или использовать умные внутри GeneralClass.

0 голосов
/ 26 октября 2018

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

 #include <memory>

 struct base {
     virtual double getValue(const int& x) const = 0;
 };

 struct impl1 : base {
     double getValue(const int& x) { return 1.0; }
 };

 struct impl2 : base {
     double getValue(const int& x) { return 2.0; }
 };
 // ... maybe more...

 enum select { impl1s, impl2s };
 base* make_impl( select s) {
     if (s == impl1s) return new impl1();
     if (s == impl2s) return new impl2();
 }

 int main() {
     std::shared_ptr<base> x{ make_impl(impl1) };
 }

Не уверен, что это то, что вы ищете. Кстати, использование <memory> не должно заставлять вас чувствовать себя неуклюже, но вместо этого вы должны гордиться тем, что у нас есть такие замечательные инструменты в c ++;).

РЕДАКТИРОВАТЬ: Если вы не хотите, чтобы пользователь работал с (умными) указателями, оберните вышеупомянутое просто в другой класс:

struct foo {
    shared_ptr<base> impl;
    foo( select s) : impl( make_impl(s) ) {}
    double getValue(const int& x) { return impl.getValue(x); }
};

теперь пользователь может сделать

int main() {
    auto f1 { impl1s };
    auto f2 { impl2s };
    f1.getValue(1);
    f2.getValue(2);
}        
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...