Функция для управления контейнером базовых / производных объектов - PullRequest
0 голосов
/ 22 апреля 2009

рассмотрим следующий алгоритм с массивами:

class MyType;
{
    // some stuff
}

class MySubType:MyType
{
    // some stuff
}

void foo(MyType** arr, int len)
{
    for (int i = 0;i<len;i++)
        // do something on arr[i]->
}

void bar()
{
    MySubType* arr[10];
    // initialize all MySubType*'s in arr
    foo(&arr, 10);
}

Ничего особенного. У меня вопрос - как мне это сделать с помощью шаблонов?

void foo(std::vector<MyType>& s)
{
    std::vector<MyType>::iterator i;
    for (i = s.begin(); i != s.end(); i++)
        // do stuff on *i
}

итак, в баре я не могу этого сделать:

void bar()
{
    std::vector<MySubType> s;
    foo(s);  // compiler error
}

ошибка: неверная инициализация ссылки типа std::vector<MyType, std::allocator<MyType> >& из выражения типа std::vector<MySubType, std::allocator<MySubType> >

Есть ли способ сделать что-то подобное?

В основном, если есть способ сделать это:

std::vector<MySubType> s;
std::vector<MyType>& t = s;

Я был бы счастлив ...

Ответы [ 9 ]

8 голосов
/ 22 апреля 2009

Это может решить вашу проблему

template <typename T>
void foo(std::vector<T>& s)
{
    typename std::vector<T>::iterator i;
    for (i = s.begin(); i != s.end(); i++)
        // do stuff on *i
}
6 голосов
/ 22 апреля 2009

Чтобы раскрыть ответ Куосона , идиоматический стиль C ++ - передавать итераторы в функцию, а не в контейнеры.

template<typename Iterator>
void foo(const Iterator & begin, const Iterator & end)
{
    Iterator i;
    for (i = begin;  i != end;  ++i)
        // do stuff on *i
}
3 голосов
/ 22 апреля 2009

Вот проблема с этим - если s и t указывают на один и тот же объект, что мешает вам поместить MyOtherSubType (не связанный с MySubType) в t? Это сделало бы объекты s объектами, которые не являются MySubType. Я не знаю ни одного типобезопасного языка программирования, который позволял бы вам это делать. Если бы это было разрешено, представьте проблемы, которые у нас были бы:

//MySubType inherits from MyType
//MyOtherSubType also inherits from MyType

std::vector<MySubType> s;
std::vector<MyType>& t = s;

MyOtherSubType o;
t.push_back(o);

Поскольку t и s - это точно один и тот же объект под разными именами, элемент в s [0] является чем-то, что не является MySubType. Огромная проблема - мы никогда не могли быть уверены, что наши векторы содержат типы, которые они должны! Таким образом, скомпилированное запрещает это.

2 голосов
/ 22 апреля 2009

Поскольку вы говорите: «Ничего особенного здесь нет», я не уверен, что вы понимаете это:

Ваш оригинальный код не работает; или, если вам повезет, он может не сломаться прямо сейчас, но он довольно хрупок и сломается, как только кто-то сделает что-то большее, чем просто посмотрите на класс MySubType.

Проблема в том, что вы передаете MyType* в foo(), но он действительно указывает на массив MySubType. Если объект MySubType окажется больше, чем объект MyType (что весьма вероятно, если вы добавили что-либо в производный класс), то арифметика указателей, выполненная в функции foo(), будет неправильной.

Это одна из серьезных и классических ловушек массивов объектов производного класса.

1 голос
/ 22 апреля 2009

Если вы хотите хранить объекты нескольких различных типов MyType в одном векторе (как я подозреваю, вы делаете, хотя в этом конкретном примере это не обязательно), вам нужно будет использовать std::vector<MyType*> std::vector<MyType>. Это предложение аналогично предложению Майкла Берра для вашего исходного кода указателя.

Это имеет неприятный побочный эффект, который вы не можете неявно преобразовать std::vector<MySubType*> в std::vector<MyType*> для вызова foo() с. Но код преобразования не слишком обременителен:

void foo(std::vector<MyType*>& s)
{
    ...
}

void bar()
{
    std::vector<MySubType*> s;

    // Populate s
    ...

    std::vector<MyType*> u(s.begin(), s.end());    // Convert
    foo(u);
}

Или просто bar() используйте std::vector<MyType*> с самого начала.

0 голосов
/ 22 апреля 2009

Если бы я знал, каков настоящий код за этими foobars, возможно, я бы знал лучше, но разве нет решения для вашей проблемы в STL?

for_each(s.begin(), s.end(), DoStuffOnI());

Просто поместите ваш код "делай вещи на * i" в функцию или функтор:

struct DoStuffOnI : public std::unary_function<MyType&,void> {
    void operator()(MyType& obj) {
        // do stuff on *i
    }
};

Если вам надоело отправлять два параметра вместо одного, тогда хорошо, возможно, вы можете сделать что-то вроде:

template<typename In>
struct input_sequence_range : public std::pair<In,In> {
    input_sequence_range(In first, In last) : std::pair<In,In>(first, last)
    {
    }
};

template<typename C>
input_sequence_range<typename C::iterator> iseq(C& c)
{
    return input_sequence_range<typename C::iterator>(c.begin(), c.end());
}

template<typename In, typename Pred>
void for_each(input_sequence_range<In> r, Pred p) {
    std::for_each(r.first, r.second, p);
}

Тогда вызывайте for_each так:

for_each(iseq(s), DoStuffOnI());
0 голосов
/ 22 апреля 2009

Использование повышения (для умного указателя):

foo( std::vector<boost::shared_ptr<MyType> >& v )
{
   std::for_each( v.begin(),
                  v.end(),
                  do_something );
}

bar()
{
    std::vector<boost::shared_ptr<MyType> > s;
    // s.push_back( boost::shared_ptr<MyType> ( new MySubType() ) );
    foo( s ); 
}
0 голосов
/ 22 апреля 2009

К сожалению, нет. Различные шаблонные специализации считаются разными классами; Вы не можете конвертировать между std::vector<MyType> и std::vector<MySubType>.

Вы можете разделить общий бит "do stuff" foo() на отдельную функцию и создать отдельный цикл для каждого вектора (или, возможно, использовать std::foreach).

0 голосов
/ 22 апреля 2009

C ++ не поддерживает ковариацию типов шаблонов, как это делает C # в настоящее время, во многом по тем же причинам. Если вы хотите сделать это, лучше всего сделать так, чтобы ваш вектор был шаблонизирован по общему типу интерфейса (указателей на этот тип), и заставить функцию foo принимать вектор этого типа.

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