Вектор указателей на базовый тип, найти все экземпляры данного производного типа, хранящиеся в базовом типе

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

class A {};

и производные классы

class B: public A {};
class C: public A {};

Теперь экземпляры B и C хранятся в std::vector boost::shared_ptr<A>:

std::vector<boost::shared_ptr<A> > A_vec;
A_vec.push_back(boost::shared_ptr<B>(new B()));
A_vec.push_back(boost::shared_ptr<C>(new C()));

Добавление экземпляров B и C выполняется пользователем, и нет возможности заранее определить порядок, в котором они будут добавлены.

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

Конечно, я могу выполнять преобразования методом проб и ошибок, т. е. пытаться выполнить приведение к B и C (и любой другой производной от базового класса), пока не найду преобразование, которое не приводит к броску. Однако этот метод кажется очень грубым и подверженным ошибкам, и я ищу более элегантный (и более эффективный) способ.

Я ищу решение, которое также будет работать с С++ 98, но может потребовать повышения функциональности.

Любые идеи ?


ИЗМЕНИТЬ:

Хорошо, спасибо за все ответы!

Я хотел бы дать более подробную информацию о прецеденте. Все это происходит в контексте параметрической оптимизации.

Пользователи определяют проблему оптимизации следующим образом:

  • Указание параметров, то есть их типов (например, «ограниченный двойной», «ограниченный целочисленный», «неограниченный двойной», «логический» и т. д.) и начальных значений
  • Указание функции оценки, которая назначает одну или несколько оценок (двойных значений) заданному набору параметров.

Затем различные алгоритмы оптимизации воздействуют на определения задач, включая их параметры.

Существует ряд предопределенных объектов параметров для обычных случаев, но пользователи также могут создавать свои собственные объекты параметров, производные от одного из моих базовых классов. Таким образом, с точки зрения библиотеки, кроме того факта, что объекты параметров должны соответствовать заданному (базовому классу) API, я не могу многого предполагать об объектах параметров.

Определение проблемы представляет собой определяемый пользователем класс C++, производный от базового класса с интерфейсом std::vector. Пользователь добавляет свои (предустановленные или доморощенные) объекты параметров и перегружает фитнес-функцию.

Доступ к объектам параметров может произойти

  • из алгоритмов оптимизации (обычно это нормально, даже для собственных объектов параметров, поскольку объекты производных параметров должны предоставлять функции доступа к своим значениям).
  • из предоставленной пользователем фитнес-функции (обычно нормально, поскольку пользователь знает, где найти объект параметра в коллекции, и его значение может быть легко доступно)

Это прекрасно работает.

Однако могут быть особые случаи, когда

  • пользователь хочет получить доступ к особенностям своих доморощенных типов параметров
  • третья сторона предоставила структуру параметров (это библиотека с открытым исходным кодом, другие могут добавлять код для конкретных задач оптимизации)
  • структура параметров (т. е. какие параметры находятся в векторе) может быть изменена как часть задачи оптимизации --> пример: обучение архитектуре нейронной сети

В этих обстоятельствах было бы здорово иметь простой метод доступа ко всем объектам параметров данного производного типа внутри набора базовых типов.

У меня уже есть шаблон "conversion_iterator". Он перебирает вектор базовых объектов и пропускает те, которые не соответствуют желаемому целевому типу. Однако это основано на преобразовании методом проб и ошибок (т. е. я проверяю, является ли преобразованный интеллектуальный указатель NULL), что я считаю очень неэлегантным и подверженным ошибкам.

Я хотел бы иметь лучшее решение.

NB: Библиотека оптимизации предназначена для случаев использования, когда этап оценки для заданного набора параметров может длиться сколь угодно долго (обычно секунды, возможно, часы или дольше). Таким образом, скорость доступа к типам параметров не имеет большого значения. Но стабильность и ремонтопригодность...


person user3423757    schedule 27.04.2014    source источник
comment
Помимо virtual методов, вы имеете в виду?   -  person    schedule 27.04.2014


Ответы (5)


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

Более того, необходимость сделать это намекает на проблему проектирования: вся цель базового класса состоит в том, чтобы иметь возможность обращаться с детьми так, как если бы они были родителями. Однако в некоторых ситуациях это необходимо, и в этом случае вы должны использовать посетитель для их отправки.

person Konrad Rudolph    schedule 27.04.2014
comment
Работает только при включенном RTTI. Ваш второй пункт верен на 100%, ИМО. - person KitsuneYMG; 27.04.2014
comment
Если RTTI не включен, это не совсем С++. - person aschepler; 27.04.2014
comment
Более того, ваша потребность сделать это намекает на проблему дизайна — полностью! - person Richard Hodges; 27.04.2014
comment
Да, typeid должен помочь (более элегантным способом, чем просто приведение), учитывая использование итератора преобразования. Будет только одно сравнение, так что я думаю, это нормально. Спасибо! Проблема перепроектирования: Необходимость в этом средстве возникает только у специальных пользователей в особых случаях. Весь остальной доступ, конечно же, происходит через базовый класс (см. мои правки в ОП). Так что я надеюсь, что это оправдано. - person user3423757; 28.04.2014

Если возможно, добавьте в класс A виртуальные методы для выполнения "конкретных действий с B и C".

Если это невозможно или нецелесообразно, используйте форму указателя dynamic_cast, чтобы не возникало исключений.

for (boost::shared_ptr<A> a : A_vec)
{
    if (B* b = dynamic_cast<B*>(a.get()))
    {
        b->do_something();
    }
    else if (C* c = dynamic_cast<C*>(a.get()))
    {
        something_else(*c);
    }
}
person aschepler    schedule 27.04.2014

Добавление экземпляров B и C выполняется пользователем, и нет возможности заранее определить порядок, в котором они будут добавлены.

Хорошо, так что просто положить их в два разных контейнера?

std::vector<boost::shared_ptr<A> > A_vec;
std::vector<boost::shared_ptr<B> > B_vec;
std::vector<boost::shared_ptr<C> > C_vec;

void add(B * p)
{
    B_vec.push_back(boost::shared_ptr<B>(p));
    A_vec.push_back(b.back());
}

void add(C * p)
{
    C_vec.push_back(boost::shared_ptr<C>(p));
    A_vec.push_back(c.back());
}

Затем вы можете перебирать Bs или Cs в свое удовольствие.

person fredoverflow    schedule 27.04.2014

Я бы предложил реализовать метод в базовом классе (например, TypeOf()), который будет возвращать тип конкретного объекта. Убедитесь, что вы определили этот метод как виртуальный и абстрактный, чтобы вам пришлось реализовывать его в производных типах. Что касается самого типа, вы можете определить перечисление для каждого типа (например, класса).

enum class ClassType { ClassA, ClassB, ClassC };
person aalimian    schedule 27.04.2014

Этот ответ может вас заинтересовать: Создание интерфейса без виртуальных функций?

Это показывает вам оба подхода

  • вариант с посетителем в одной коллекции
  • отдельные коллекции,

как было предложено другими (в частности, Фредом и Конрадом). Последний более эффективен для итерации, первый может быть более чистым и удобным в сопровождении. Это может быть даже более эффективным, в зависимости от шаблонов использования.

person sehe    schedule 27.04.2014