c ++ проблема с полиморфизмом и векторами указателей - PullRequest
3 голосов
/ 16 ноября 2009

Рассмотрим следующий пример кода:

class Foo
{
};

class Bar : public Foo
{
};

class FooCollection
{
protected:
    vector<shared_ptr<Foo> > d_foos;
};

class BarCollection : public FooCollection
{
public:
    vector<shared_ptr<Bar> > &getBars()
    {
        // return d_foos won't do here...
    }
};

У меня такая проблема в моем текущем проекте. Код клиента использует BarCollection, в котором указатели хранятся в Bars в d_foos, который объявлен в FooCollection. Теперь я хотел бы представить коллекцию указателей на Bars клиентскому коду. Я мог бы просто предоставить клиентскому коду доступ к вектору указателей на Foo с и привести их к указателям на Bar с в клиентском коде, но это неправильно, так как клиент не должен знать о Foo существование.

Я также мог бы определить get() член, который извлекает объекты из d_foos и приводит их в действие, но это выглядит довольно неуклюже. Желательно, чтобы я просто возвращал d_foos как vector<shared_ptr<Bar> > &, но я не могу этого сделать.

Также может быть, что мой дизайн просто неверен. Это казалось наиболее естественным решением, поскольку Bar - это специализация Foo, а BarCollection - это специализация FooCollection, и они имеют общие функции.

Не могли бы вы предложить хорошие решения для реализации getBars в BarCollection или лучшие варианты дизайна?

Edit:

Оказывается, мой дизайн действительно был плохим. BarCollection не является FooCollection, несмотря на то, что требуется вся функциональность FooCollection. Мое текущее решение, основанное на ответах ниже, которое намного чище, теперь:

class Foo
{
};

class Bar : public Foo
{
};

template<class T>
class Collection
{
    vector<shared_ptr<T> > d_items;
};

typedef Collection<Foo> FooCollection;

class BarCollection : public Collection<Bar>
{
    // Additional stuff here.
};

Спасибо за отличные предложения и примеры!

Ответы [ 7 ]

3 голосов
/ 16 ноября 2009

Я бы предложил выставлять итераторы из ваших контейнерных классов вместо контейнера-члена. Таким образом, не имеет значения, какой тип контейнера.

3 голосов
/ 16 ноября 2009

Проблема в том, что вы пытаетесь смешивать и сочетать два разных, в значительной степени независимых вида полиморфизма таким образом, что это не сработает. Безопасный для типов полиморфизм шаблонов во время компиляции не позволит вам заменить базовый тип производным типом. Система шаблонов C ++ никак не связана между

class<Foo>

и

class<Bar>

Одним из предложений может быть создание производного адаптера Foo, который приведёт к правильному классу:

 template <class derived, class base>
 class DowncastContainerAdapter
 {
 private:
     std::vector< boost::shared_ptr<base> >::iterator curr;
     std::vector< boost::shared_ptr<base> >::const_iterator end;
 public:
     DowncastContainerAdapter(/*setup curr & end iterators*/)
     {
         // assert derived actually is derived from base
     }

     boost::shared_ptr<derived> GetNext()
     {
         // increment iterator
         ++curr;
         return dynamic_cast<base>(*curr);
     }

     bool IsEnd()
     {
         return (curr == end);
     }
 };

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

Другая мысль

Вы можете не осознавать этого, но, возможно, было бы прекрасно просто вернуть вектор Foo. Пользователь Bar уже должен иметь полное представление о Foo, поскольку, включая Bar.h, он должен получить Foo.h через Bar.h. Причина в том, что для того, чтобы Bar наследовал от Foo, он должен иметь полное представление о классе через Foo.h. Я бы посоветовал вместо того, чтобы использовать решение выше, если это возможно, сделать Foo (или суперкласс Foo) интерфейсным классом и передать векторы указателей этому интерфейсному классу. Это довольно часто встречающийся шаблон, и он не удивит, что это чудное решение, которое я придумал, может :). С другой стороны, у вас могут быть свои причины. Удачи в любом случае.

2 голосов
/ 16 ноября 2009

Вопрос в том, зачем вы это делаете? Если вы предоставляете пользователю коллекцию указателей на Bar, вы предполагаете, что в ней есть только Bars, поэтому внутреннее хранение указателей в коллекции на Foo не имеет смысла. Если вы храните разные подтипы Foo в своей коллекции указателей на Foo, вы не можете вернуть их как коллекцию указателей на Bar, так как не все объекты в ней являются Bars. В первом случае (вы знаете, что у вас есть только бары) вы должны использовать шаблонный подход, как предложено выше. В противном случае вам придется переосмыслить то, что вы действительно хотите.

2 голосов
/ 16 ноября 2009
template<class T>
class MyContainer {
  vector<shared_ptr<T> > d_foos;
public:
  vector<shared_ptr<T> > & getVector();
};

class FooCollection : public MyContainer<Foo> {
};

class BarCollection : public MyContainer<Bar> {
};
1 голос
/ 16 ноября 2009

Прежде всего, давайте поговорим о shared_ptr. Знаете ли вы о: boost::detail::dynamic_cast_tag?

shared_ptr<Foo> fooPtr(new Bar());
shared_ptr<Bar> barPtr(fooPtr, boost::detail::dynamic_cast_tag());

Это очень удобный способ. Под прикрытием он просто выполняет dynamic_cast, ничего особенного, кроме более простой записи. Контракт такой же, как и классический: если указанный объект на самом деле не является Bar (или получен из него), то вы получите нулевой указатель.

Вернуться к вашему вопросу: ПЛОХОЙ КОД.

BarCollection НЕ является FooCollection, как уже упоминалось, у вас возникли проблемы, потому что вы могли бы ввести другие элементы в вектор указателей, такие как Bar.

Я не буду останавливаться на этом, хотя, потому что это выходит за рамки рассматриваемого вопроса, и я думаю, что мы (как те, кто пытается ответить) должны сдерживать себя от этого.

Вы не можете передать ссылку, но вы можете передать View.

По сути, View - это новый объект, который действует как Proxy для старого. Относительно легко использовать Boost.Iterators из примера.

class VectorView
{
  typedef std::vector< std::shared_ptr<Foo> > base_type;

public:
  typedef Bar value_type;
  // all the cluttering

  class iterator: boost::iterator::iterator_adaptor<
    iterator,
    typename base_type::iterator,
    std::shared_ptr<Bar>
  >
  {
    typename iterator_adaptor::reference dereference() const
    {
      // If you have a heart weakness, you'd better stop here...
      return reinterpret_cast< std::shared_ptr<Bar> >(this->base_reference());
    }
  };

  // idem for const_iterator

  // On to the method forwarding
  iterator begin() { return iterator(m_reference.begin()); }

private:
  base_type& m_reference;
}; // class VectorView

Настоящая проблема здесь, конечно, reference бит. Получить объект NEW shared_ptr легко и можно выполнить dynamic_cast по мере необходимости. Получение reference до ORIGINAL shared_ptr, но интерпретируемого как требуемый тип ... это действительно не то, что мне нравится видеть в коде.

Примечание
Возможно, есть способ добиться большего успеха, чем при использовании класса Boost.Fusion transform_view , но я не могу понять это.

В частности, используя transform_view, я могу получить shared_ptr<Bar>, но не могу получить shared_ptr<Bar>&, когда разыменую свой итератор, что раздражает, учитывая, что единственное использование - возвращение ссылки на базовый vector (а не const_reference) - фактически изменить структуру vector и объектов, которые он содержит.

Примечание 2 :
Пожалуйста, подумайте о рефакторинге. Там были отличные предложения.

1 голос
/ 16 ноября 2009

Есть ли у вас особая необходимость иметь BarCollection, полученную из FooCollection? Поскольку обычно BarCollection не является a FooCollection, обычно многие вещи, которые можно выполнить с помощью FooCollection, не следует выполнять с помощью BarCollection. Например:

BarCollection *bc = new BarCollection();
FooCollection *fc = bc; // They are derived from each other to be able to do this
fc->addFoo(Foo());      // Of course we can add a Foo to a FooCollection

Теперь мы добавили Foo объект к тому, что должно быть BarCollection. Если BarCollection попытается получить доступ к этому вновь добавленному элементу и ожидает, что он будет Bar, произойдут все виды уродливых вещей.

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

1 голос
/ 16 ноября 2009

Разве вы не можете заменить это коллекцией, шаблонизированной на Foo / Bar ?, что-то вроде этого

class Collection<T> {
protected:
    vector<shared_ptr<T> > d_foos;
};

typedef Collection<Foo> FooCollection;
typedef Collection<Bar> BarCollection;
...