Приведение контейнера shared_ptr - PullRequest
2 голосов
/ 06 мая 2010

У меня есть метод

void foo(list<shared_ptr<Base>>& myList); 

Который я пытаюсь вызвать с помощью двух разных типов списков, одного из DerivedClass1 и одного из DerivedClass2

list<shared_ptr<DerivedClass1>> myList1; 
foo(myList1);
list<shared_ptr<DerivedClass2>> myList2; 
foo(myList2);

Однако это, очевидно, приводит к ошибке компилятора

error: a reference of type "std::list<boost::shared_ptr<Base>, std::allocator<boost::shared_ptr<Base>>> &" (not const-qualified) cannot be initialized with a value of type "std::list<boost::shared_ptr<DerivedClass1>, std::allocator<boost::shared_ptr<DerivedClass1>>>"

Есть ли простой способ разыграть контейнер shared_ptr? Из альтернативных контейнеров, которые могут это сделать?

Обновление : Спасибо всем, кто откликнулся. Работая в пределах языка, кажется, что лучший способ сохранить метод «как есть» - это использовать контейнер shared_ptr и передать его точно (создание нового списка на сайте вызова).

Полагаю, я уже почти знал это, но я вспомнил, как читал о других частях библиотеки boost, связанных с контейнерами shared_ptr, и подумал, что, может быть, кто-то другой уже решил более элегантно. Однако из моих собственных дальнейших исследований они, похоже, больше направлены на снижение накладных расходов на shared_ptr в тех случаях, когда несколько указателей принадлежат исключительно (следовательно, требуется одна блокировка на контейнер, а не одна на объект в контейнере).

Еще раз спасибо, вы, ребята, все классные!

Ответы [ 4 ]

7 голосов
/ 06 мая 2010

Измените foo, чтобы оно соответствовало соглашениям STL и принимало итераторы:

template< typename It >
void foo(It begin, It end); 

Затем передайте итераторы в свои списки

list<shared_ptr<DerivedClass1>> myList1;
foo(myList1.begin(), myList1.end());
list<shared_ptr<DerivedClass2>> myList2;
foo(myList2.begin(), myList2.end());

И все должно работать просто отлично.

Редактировать :
Обратите внимание, что это не что-то особенное для умных указателей. std::list<T1*>& нельзя инициализировать с помощью std::list<T2*>, даже если T2 происходит от T1.

3 голосов
/ 06 мая 2010

Нельзя привести контейнер одного типа к контейнеру другого типа. Существует несколько способов создать новый контейнер из существующего контейнера, если тип объекта, сохраненного существующим контейнером, можно преобразовать в тип объекта, сохраненного новым контейнером:

Вы можете использовать std::copy, чтобы сделать преобразование элемент за элементом:

list<shared_ptr<Base> > baseList;
list<shared_ptr<Derived> > derivedList;
std::copy(derivedList.begin(), derivedList.end(), std::back_inserter(baseList));

Вы также можете напрямую создать baseList, используя начальный и конечный итераторы из derivedList:

list<shared_ptr<Base> > baseList(derivedList.begin(), derivedList.end());

Если ваша функция может вместо этого взять константную ссылку, вы можете создать временную в вызове функции:

typedef list<shared_ptr<Base> > BaseList;
foo(BaseList(derivedList.begin(), derivedList.end()));
1 голос
/ 06 мая 2010

Обратите внимание, что list<shared_ptr<Base>> и list<shared_ptr<DerivedClass1>> (или даже shared_ptr<Base> и shared_ptr<DerivedClass1>) - это совершенно разные типы, даже если DerivedClass1 наследуется от Base.Поэтому приведение одного к другому на самом деле было бы неправильным.

Рассмотрим случай, когда foo пытается добавить новые элементы типа Base в myList.Если бы вы могли привести myList1 в list<shared_ptr<Base>> (например, с использованием C-типа в старом стиле) и передать его в foo, результат был бы нехорошим.см. это создание нового list<shared_ptr<Base>> объекта и копирование в него содержимого другого списка, а затем передача его в foo, как демонстрирует @James McNellis.

0 голосов
/ 06 мая 2010

Классическое объяснение, почему это не будет хорошей идеей, выглядит следующим образом:

Если у вас есть list<Apple*>, красиво заполненный яблоками, и вы смогли передать его функции, принимающей list<Fruit*>, то функция не помешает добавить новые апельсины в список. list<Fruit*> просто не может заменить list<Apple*> и не существует никакой связи.

Если вам нужна функция, которая могла бы работать как с list<Apple*>, так и с list<Orange*>, вы можете использовать шаблоны (будь то с Итераторами, или с самим Контейнером, или с list<T*> - полностью ваш выбор.


Если вы очень сильно хотели передать list<Apple*>, как если бы list<Fruit*>, тогда я полагаю, что технически возможно было бы создать прокси для контейнера / итераторов, которые используют стирание типа и что-не для приведения внутренние указатели / остановка недопустимых приведений (помещение апельсинов в список яблок), чтобы вы могли вместо этого сделать функцию, взяв list_proxy<Fruit*>, которая принимает списки производных типов.

Что-то вроде (только начало):

#include <list>
#include <boost/shared_ptr.hpp>

template <class T>
class ListWrapperBaseAux
{
public:
    virtual ~ListWrapperBaseAux() {}
    virtual T* front() = 0;
};

template <class T, class U>
class ListWrapperAux: public ListWrapperBaseAux<T>
{
    std::list<U*>* list_ref;
public:
    ListWrapperAux(std::list<U*>* list_ref): list_ref(list_ref) {}
    virtual T* front() { return dynamic_cast<T*>(list_ref->front()); }
};

template <class T>
class ListProxy
{
    boost::shared_ptr<ListWrapperBaseAux<T> > list_ref;
public:
    template <class U>
    ListProxy(std::list<U*>& li): list_ref(new ListWrapperAux<T, U>(&li)) {}
    T* front() { return list_ref->front(); }
};

struct Fruit
{
    virtual ~Fruit() {}
};

struct Apple: Fruit {};
struct Orange: Fruit {};

void bake(ListProxy<Fruit> li)
{
    Fruit* f = li.front();
}

int main()
{
    std::list<Apple*> apples;
    bake(apples);

    std::list<Orange*> oranges;
    bake(oranges);
}
...