Получение вектора <Derived *> в функцию, которая ожидает вектор <Base *> - PullRequest
25 голосов
/ 22 сентября 2008

Рассмотрим эти классы.

class Base
{
   ...
};

class Derived : public Base
{
   ...
};

эта функция

void BaseFoo( std::vector<Base*>vec )
{
    ...
}

И, наконец, мой вектор

std::vector<Derived*>derived;

Я хочу передать derived в функцию BaseFoo, но компилятор не позволяет мне. Как мне решить эту проблему, не копируя весь вектор в std::vector<Base*>?

Ответы [ 9 ]

44 голосов
/ 22 сентября 2008

vector<Base*> и vector<Derived*> являются несвязанными типами, поэтому вы не можете сделать это. Это объясняется в C ++ FAQ здесь .

Вам нужно изменить переменную с vector<Derived*> на vector<Base*> и вставить в нее Derived объектов.

Кроме того, чтобы избежать ненужного копирования vector, вы должны передавать его по const-ссылке, а не по значению:

void BaseFoo( const std::vector<Base*>& vec )
{
    ...
}

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

#include <boost/ptr_container/ptr_vector.hpp>
boost::ptr_vector<Base> vec;

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

#include <memory>
std::vector< std::shared_ptr<Base*> > vec;

или

#include <boost/shared_ptr.hpp>
std::vector< boost::shared_ptr<Base*> > vec;

В каждом случае вам необходимо соответствующим образом изменить функцию BaseFoo.

23 голосов
/ 22 сентября 2008

Вместо передачи объекта контейнера (vector<>) передайте итераторы begin и end, как и остальные алгоритмы STL. Функция, которая их получает, будет шаблонной, и не имеет значения, если вы передадите в Derived * или Base *.

14 голосов
/ 22 сентября 2008

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

8 голосов
/ 22 сентября 2008

один вариант - использовать шаблон

template<typename T>
void BaseFoo( const std::vector<T*>& vec)
{
 ...
}

Недостатком является то, что реализация должна быть в заголовке, и вы получите небольшой раздувшийся код. Вы получите разные функции для каждого типа, но код останется прежним. В зависимости от варианта использования это быстрое и грязное решение.

Правка, я должен отметить, что нам нужен шаблон здесь, потому что мы пытаемся написать тот же код для несвязанных типов, как отмечено несколькими другими авторами. Шаблоны позволяют вам решить именно эти проблемы. Я также обновил его, чтобы использовать константную ссылку. Вы также должны передавать «тяжелые» объекты, такие как вектор, по константной ссылке, когда вам не нужна копия, что в основном всегда.

2 голосов
/ 22 сентября 2008

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

// BaseFoo.h
template<typename T>
void BaseFoo( const std::vector<T*>& vec);

// BaseFoo.cpp
template<typename T>
void BaseFoo( const std::vector<T*>& vec);
{
 ...
}

// Explicit instantiation means no need for definition in the header file.
template void BaseFoo<Base> ( const std::vector<Base*>& vec );
template void BaseFoo<Derived> ( const std::vector<Derived*>& vec );
2 голосов
/ 22 сентября 2008

Обычно вы начинаете с контейнера базовых указателей, а не наоборот.

1 голос
/ 22 сентября 2008

Если std::vector поддерживает то, что вы просите, то можно будет победить систему типов C ++ без использования приведений (правка: ссылка ChrisN на C ++ FAQ Lite говорит об этой же проблеме):

class Base {};
class Derived1 : public Base {};
class Derived2 : public Base {};

void pushStuff(std::vector<Base*>& vec) {
    vec.push_back(new Derived2);
    vec.push_back(new Base);
}

...
std::vector<Derived1*> vec;
pushStuff(vec); // Not legal
// Now vec contains a Derived2 and a Base!

Поскольку ваша функция BaseFoo() принимает вектор по значению, она не может изменить исходный вектор, который вы передали, поэтому то, что я написал, было бы невозможно. Но если для этого требуется неконстантная ссылка и вы используете reinterpret_cast<std::vector<Base*>&>() для передачи std::vector<Derived*>, вы можете не получить желаемый результат, и ваша программа может аварийно завершить работу.

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

1 голос
/ 22 сентября 2008

Если вы имеете дело со сторонней библиотекой, и это ваша единственная надежда, то вы можете сделать это:

BaseFoo (*reinterpret_cast<std::vector<Base *> *>(&derived));

В противном случае исправьте ваш код с одним из других предложений.

0 голосов
/ 22 сентября 2008

Они не связаны между собой - вы не можете.

...