2009-11-16 3 views
3

Рассмотрим следующий пример кода:с ++ проблема с полиморфизмом и векторы указателей

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 s в клиентском коде, но это кажется неправильным, так как клиент не должен знать о существовании 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. 
}; 

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

+0

Я опубликовал решение ... в основном для полноты, хотя он включает в себя 'reinterpret_cast'. Я настоятельно призываю вас рассмотреть альтернативы рефакторинга. В частности, вы перепутали отношения IS-A, введенные путем наследования с повторным использованием кода. Кроме того, вы не хотите иметь 'getBars'. Раскрытие ваших личных частей считается эксгибиционизмом и наказуемым законом. –

+0

@Matthieu: Я добавил свое реорганизованное текущее решение проблемы. Благодаря! –

ответ

2
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

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

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

typedef Collection<Foo> FooCollection; 
typedef Collection<Bar> BarCollection; 
+0

Возможно, нет, если вы хотите предоставить полиморфный метод. –

3

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

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) классом интерфейса и передать векторы указателей на этот класс интерфейса. Это довольно часто встречающийся паттерн и не поднимает брови, что это неуклюжие решения, которые я придумал :). Опять же, у вас могут быть свои причины. Удачи в любом случае.

+0

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

3

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

+0

Вы не можете ошибаться. Dereferencing 'std :: vector :: iterator' все еще дает 'Foo &', что не является тем, что вы хотите для 'BarCollection'. –

+0

Я имел в виду пользовательские итераторы –

2

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

1

У вас есть специальная необходимость иметь 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, все виды уродливых вещей произойдут.

Как правило, вы хотите избежать этого и не иметь классов сбора, полученных друг от друга. См. Также questions около casting containers производных типов для получения дополнительных ответов на эту тему ...

+0

Хороший пример того, почему мой первоначальный дизайн был плохим. +1! –

1

Прежде всего, давайте поговорим о 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 (или полученным из него), тогда вы получаете нулевой указатель.

Назад к вопросу: BAD CODE.

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 бит. Получение объекта NEWshared_ptr прост и позволяет выполнять, при необходимости, dynamic_cast. Получение referenceORIGINALshared_ptr, но интерпретируемый как необходимый тип ... на самом деле не то, что мне нравится видеть в коде.

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

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

Примечание 2:
Просьба рассмотреть вопрос о реорганизации. Там были отличные предложения.