Шаблоны C ++ полиморфизм препятствие - PullRequest
6 голосов
/ 01 февраля 2012

Интерфейс:

template <class T>
class Interface{
    public:
    typedef T Units;
    virtual T get() = 0;
};

выполнения решений1:

class Implementation1: public Interface<float> {
    public:

    float get() {
       return 0.0f;
    }

};

выполнения решений2:

class Implementation2: public Interface<int> {
    public:

    int get() {
       return 0;
    }

};

Контейнер (с ошибками):

class Container{
    private:
    Interface* floatGetter;
    int n;
    Timer::Units* array;

    public:
    Container(Interface* floatGetter, int n) {
        this->floatGetter= floatGetter;
        this->n = n;
        array = new Timer::Units[n];
    }

    ~Container() {

    }

};

Для получения более подробной информации у меня есть интерфейс шаблона и производный класс от этого интерфейса без шаблона. Некоторые другие классы принимают объект производного класса, но он принимает объект в качестве интерфейса (другими словами, внедрение зависимости). Но тип интерфейса в этом классе определяется реализацией интерфейса. Как реализовать эту идею в C ++?

Edit1:

Пример:

Interface<float> myInterface1 = new Implementation1();
Interface<int> myInterface2 = new Implementation2();
Container container1 = new Container(myInterface1, 10);
Container container2 = new Container(myInterface2, 10);

Мне нужно, чтобы контейнер понимал аргумент шаблона интерфейса из его реализации.

Ответы [ 2 ]

6 голосов
/ 01 февраля 2012

ОК, сначала объяснение проблемы здесь. Требуется интерфейс, который определяет виртуальный метод, используемый для получения значения с шаблонным типом. Поскольку нам нужен интерфейс, метод get должен быть виртуальным. С другой стороны, мы хотели бы иметь возможность возвращать разные типы, поэтому мы хотим его храмовать. Однако виртуальный метод не может быть хранимым, потому что компилятор не будет знать, какие экземпляры этого метода следует включить в vtable.

Одно из решений состоит в том, чтобы сделать то, что сделано в вопросе, то есть храмовать интерфейсный класс. Важным свойством типов шаблонов является то, что разные экземпляры одного и того же класса являются совершенно разными типами. У них нет общей базы, и они не могут быть преобразованы друг в друга. У нас просто не может быть указателя Interface<Generic> в обычных функциях с вызовом их методов get (). Учтите это: каждый экземпляр типа шаблона интерфейса имеет различную сигнатуру для метода get (). Это означает, что во время вызова этого метода в стеке должны происходить разные вещи. Как мог компилятор знать, какую версию метода get () вызывать (как подготовить стек для вызова функции), если у него есть только указатель Interface<Generic>.

Я могу придумать два общих решения этой проблемы.

  1. Удалите все шаблоны mumbo-jumbo и заставьте метод get () возвращать стертый тип объекта, такой как boost :: option или boost :: any. Поправьте меня, если я ошибаюсь (*), но boost :: variable похож на объединение, которое запоминает, какой тип объединения назначен, а boost :: any похоже на void *, но запоминает, на какой тип оно указывает , Этот путь решения подразумевает две вещи: a) Типы возвращаемых объектов будут разрешены во время выполнения, и при манипулировании этими типами будут некоторые накладные расходы. b) Дочерние классы Interface должны будут управлять одним из этих стертых объектов, что усложняет их.

  2. Возьмите шаблон mumbo-jumbo до крайности и обращайтесь к объектам интерфейса всегда в замкнутом контексте, чтобы компилятор генерировал правильные вызовы функций во время реализации этих контекстов. Ниже приведен пример, который следует этому пути. В этом примере создается контейнер для хранения различных типов объектов Interface <>, в то же время позволяя применять к ним хранимые функционалы (правильно ли это называть «посетителями»?). Обратите внимание, что в этом примере объекты Interface с разными параметрами типов фактически хранятся в разных std :: lists в этом классе контейнера, поэтому во время выполнения нет необходимости разрешать их типы.

Отказ от ответственности: что излишне ...

Вот как вы можете получить контейнер класса шаблона "interface" с различными аргументами шаблона. Я использовал std :: list для хранения экземпляров, но вы можете изменить его.

#include<boost/fusion/container/vector.hpp>
#include<boost/fusion/algorithm.hpp>
#include<boost/mpl/transform.hpp>
#include<boost/mpl/contains.hpp>
#include<boost/utility/enable_if.hpp>
#include<boost/type_traits/add_reference.hpp>
#include<list>
#include<algorithm>
#include <iostream>

using namespace boost;

template <class T>
class Interface{
    public:
    typedef T Units;
    virtual T get() = 0;
};

class Implementation1: public Interface<float> {
    public:

    float get() {
       return 0.0f;
    }

};

class Implementation2: public Interface<int> {
    public:

    int get() {
       return 5;
    }

};

template<class element>
struct to_list {
    typedef std::list<Interface<element> *> type;
};

template<class elementVector>
struct to_containers {
    typedef typename mpl::transform<elementVector,to_list<mpl::_1> >::type type;
};

class Container{
    typedef fusion::vector<int,float> AllowedTypes;
    typename to_containers<AllowedTypes>::type containers;

public:
    template<class type> typename enable_if<mpl::contains<AllowedTypes,type>,void>::type 
    /*void*/ add(Interface< type/*included in AllowedTypes*/ > & floatGetter) {
        fusion::deref(fusion::find<typename to_list<type>::type >(containers))
            /*<type> container*/.push_back(&floatGetter);
    }

    template<class functional>
    void apply(functional f) {
        fusion::for_each(containers,applyFunctional<functional>(f));
    }

private:
    template<class functional>
    struct applyFunctional {
        functional f;
        applyFunctional(functional f): f(f){}
        template<class T> void operator()(T & in) const {
            std::for_each(in.begin(), in.end(),f);
        }
    };

};

struct printValueFunctional {
    template<class element>
    void operator()(Interface<element> * in) const {
        std::cout<<"Hi, my value is:"<<in->get()<<"\n";
    }
};

int main() {

    Implementation1 impl1;
    Implementation2 impl2;
    Interface<float> &myInterface1 = impl1;
    Interface<int> &myInterface2 = impl2;
    Container container;
    container.add(myInterface1);
    container.add(myInterface2);
    container.apply(printValueFunctional());
    return 0;
}

И вывод:

Hi, my value is:5
Hi, my value is:0

Ну, это действительно огромный перебор для большинства приложений, но вы просили об этом:)

Если вам просто нужен интерфейс, который может возвращать разные вещи, вы также можете рассмотреть вариант boost.variant. Приведенный выше пример действительно полезен для всего статического полиморфизма, который он использует.

РЕДАКТИРОВАТЬ: Дэвид указал на что-то важное, это может быть ловушкой, если по какой-то причине вы предполагаете иначе. Этот контейнер на самом деле не остается верным порядку вставки предметов. Порядок ваших функциональных вызовов может не совпадать с порядком вставки элементов, то есть предполагается, что итерация будет в «случайном» порядке.

(*) boost :: version и boost :: any обсуждаются здесь

4 голосов
/ 01 февраля 2012

Interface - это шаблон, а не тип. Переменные в вашем классе должны быть экземпляром шаблона с определенным типом, как:

class Container {
    Interface<float> *floatGetter;

И аналогично аргументу конструктора.

Примечание: ваш деструктор должен освободить ресурсы, которые обрабатывает ваш класс.

Примечание 2: довольно сложно написать тип, который напрямую управляет более чем одним ресурсом, рассмотрите возможность использования интеллектуальных указателей для хранения ваших данных.

Примечание 3: изучите и используйте списки инициализации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...