Подтвердить что ты не робот

Полиморфизм шаблонов С++

У меня есть эта структура классов.

class Interface{
...
}

class Foo : public Interface{
...
}

template <class T>
class Container{
...
}

И у меня есть этот конструктор другого класса Bar.

Bar(const Container<Interface> & bar){
...
}

Когда я вызываю конструктор таким образом, я получаю ошибку "отсутствие соответствия".

Container<Foo> container ();

Bar * temp = new Bar(container);

Что не так? Не являются ли шаблоны полиморфными?

4b9b3361

Ответ 1

Я думаю, что точная терминология для того, что вам нужно, это "шаблонная ковариация", что означает, что если B наследует от A, то как-то T<B> наследуется от T<A>. Это не относится к С++ и не относится к Java и С# generics *.

Существует хорошая причина избежать ковариации шаблонов: это просто удалит всю безопасность типов в классе шаблона. Позвольте мне объяснить в следующем примере:

//Assume the following class hierarchy
class Fruit {...};

class Apple : public Fruit {...};

class Orange : public Fruit {...};

//Now I will use these types to instantiate a class template, namely std::vector
int main()
{
    std::vector<Apple> apple_vec;
    apple_vec.push_back(Apple()); //no problem here

    //If templates were covariant, the following would be legal
    std::vector<Fruit> & fruit_vec = apple_vec;

    //push_back would expect a Fruit, so I could pass it an Orange
    fruit_vec.push_back(Orange()); 

    //Oh no! I just added an orange in my apple basket!
}

Следовательно, вы должны рассматривать T<A> и T<B> как полностью несвязанные типы, независимо от отношения между A и B.

Итак, как вы могли решить проблему, с которой сталкиваетесь? В Java и С# вы можете использовать соответственно ограниченные подстановочные знаки и ограничения:

//Java code
Bar(Container<? extends Interface) {...}

//C# code
Bar<T>(Container<T> container) where T : Interface {...}

Следующий стандарт С++ (известный как С++ 1x (ранее С++ 0x)) первоначально содержал еще более мощный механизм с именем Concepts, которые позволили бы разработчикам применять синтаксические и/или семантические требования к параметрам шаблона, но, к сожалению, были перенесены на более позднюю дату. Однако Boost имеет концепцию Check library, которая может вас заинтересовать.

Тем не менее, концепции могут быть немного переполнены проблемой, с которой вы сталкиваетесь, использование простого static assert, предложенное @gf, вероятно, является лучшим решением.

* Обновление. Начиная с .Net Framework 4, можно отметить, что общие параметры ковариантные или контравариантные.к югу >

Ответ 2

Здесь есть две проблемы: конструкции по умолчанию имеют форму MyClass c;; с круглыми скобками он выглядит как объявление функции компилятору.

Другая проблема заключается в том, что Container<Interface> - это просто другой тип, тогда Container<Foo> - вы могли бы сделать следующее, чтобы фактически получить полиморфизм:

Bar::Bar(const Container<Interface*>&) {}

Container<Interface*> container;
container.push_back(new Foo);
Bar* temp = new Bar(container);

Или, конечно, вы можете сделать Bar или его конструктор шаблоном, как показал Корнель.

Если вы действительно хотите некоторый полиморфизм времени компиляции с типом, вы можете использовать Boost.TypeTraits is_base_of или какой-то эквивалент:

template<class T>
Bar::Bar(const Container<T>& c) {
    BOOST_STATIC_ASSERT((boost::is_base_of<Interface, T>::value));
    // ... will give a compile time error if T doesn't 
    // inherit from Interface
}

Ответ 3

Нет. Представьте, что параметр контейнера "жестко закодирован" в класс, который он определяет (и на самом деле это работает). Следовательно, тип контейнера Container_Foo, который несовместим с Container_Interface.

Однако вы можете попробовать следующее:

template<class T>
Bar(const Container<T> & bar){
...
}

Тем не менее, вы теряете непосредственный контроль типа таким образом.

На самом деле способ STL (вероятно, более эффективный и общий) состоял бы в том, чтобы делать

template<class InputIterator>
Bar(InputIterator begin, InputIterator end){
...
}

... но я предполагаю, что у вас нет итераторов, реализованных в контейнере.

Ответ 4

Можно создать дерево наследования для контейнеров, отражающее дерево наследования данных. Если у вас есть следующие данные:

class Interface {
public:
    virtual ~Interface()
        {}
    virtual void print() = 0;
};

class Number : public Interface {
public:
    Number(int value) : x( value )
        {}
    int get() const
        { return x; }
    void print()
        { std::printf( "%d\n", get() ); };
private:
    int x;
};

class String : public Interface {
public:
    String(const std::string & value) : x( value )
        {}
    const std::string &get() const
        { return x; }
    void print()
        { std::printf( "%s\n", get().c_str() ); }
private:
    std::string x;
};

У вас также могут быть следующие контейнеры:

class GenericContainer {
public:
    GenericContainer()
        {}
    ~GenericContainer()
        { v.clear(); }

    virtual void add(Interface &obj)
        { v.push_back( &obj ); }
    Interface &get(unsigned int i)
        { return *v[ i ]; }
    unsigned int size() const
        { return v.size(); }
private:
    std::vector<Interface *> v;
};

class NumericContainer : public GenericContainer {
public:
    virtual void add(Number &obj)
        { GenericContainer::add( obj ); }
    Number &get(unsigned int i)
        { return (Number &) GenericContainer::get( i ); }
};

class TextContainer : public GenericContainer {
public:
    virtual void add(String &obj)
        { GenericContainer::add( obj ); }
    String &get(unsigned int i)
        { return (String &) GenericContainer::get( i ); }
};

Это не лучший код; это просто дать идею. Единственная проблема с этим подходом заключается в том, что каждый раз, когда вы добавляете новый класс Data, вы также должны создавать новый контейнер. Кроме того, у вас есть полиморфизм "снова работает". Вы можете быть конкретным или общим:

void print(GenericContainer & x)
{
    for(unsigned int i = 0; i < x.size(); ++i) {
        x.get( i ).print();
    }
}

void printNumbers(NumericContainer & x)
{
    for(unsigned int i = 0; i < x.size(); ++i) {
        printf( "Number: " );
        x.get( i ).print();
    }
}

int main()
{
    TextContainer strContainer;
    NumericContainer numContainer;
    Number n( 345 );
    String s( "Hello" );

    numContainer.add( n );
    strContainer.add( s );

    print( strContainer );
    print( numContainer );
    printNumbers( numContainer );
}

Ответ 5

Я предлагаю следующее обходное решение, в котором используется функция шаблона. Хотя в примере используется Qt QList, ничто не мешает правильному переносу решения в любой другой контейнер.

template <class D, class B> // D (Derived) inherits from B (Base)
QList<B> toBaseList(QList<D> derivedList)
{
    QList<B> baseList;
    for (int i = 0; i < derivedList.size(); ++i) {
        baseList.append(derivedList[i]);
    }
    return baseList;
}

Плюсы:

  • вообще
  • типобезопасный
  • достаточно эффективен, если элементы являются указателями или некоторыми другими дешево копируемыми элементами (например, неявно разделяемыми классами Qt)

Минусы:

  • требует создания нового контейнера, в отличие от повторного использования исходного
  • подразумевает некоторые издержки памяти и процессора как для создания, так и для заполнения нового контейнера, которые в значительной степени зависят от стоимости конструктора-копии

Ответ 6

контейнер - это контейнер объектов Foo, а не контейнер объектов интерфейса

И это тоже не может быть полиморфным, указатели на вещи могут быть, но не объекты themselvs. Насколько велики слоты в контейнере должны быть для контейнера, если вы могли бы положить что-нибудь из интерфейса в нем

вам нужно

 container<Interface*>

или лучше

 container<shared_ptr<Interface> >