C++ Коллекция экземпляров, реализующих чистый виртуальный класс

Я работаю на кроссплатформенном С++, и некоторые классы определены так: (сильно упрощено для этого примера)

class ExampleBase
{
public:
    ExampleBase( int blah ) : blah_test(blah) { }

    virtual void DoSomething( ) = 0;
private:
    int blah_test;
};

class ExampleImplementer : public ExampleBase
{
public:
    ExampleImplementer( ) : ExampleBase( 5 ) { }

    virtual void DoSomething( ) { /* unique implementation here */ }
};

Первоначально у меня был только один класс, несколько экземпляров которого я сохранил в std::vector и передал по ссылке const. Но теперь мне нужен базовый класс (который я хочу сохранить чисто виртуальным) и ряд полиморфных классов реализации.

Каков наилучший способ иметь коллекцию любых экземпляров реализации и при этом иметь простое управление памятью без утечек, например выделение стека std::vector?

  • Очевидно, у меня не может быть std::vector‹ ExampleBase > сейчас, поскольку std::vector требует, чтобы класс был не чисто виртуальным (поскольку он выполняет внутреннее размещение/копирование и т. д.). Я не хочу, чтобы пользователи моего кода случайно создавали экземпляры ExampleBase, потому что это неправильно. Я также хочу держаться подальше от любых возможностей нарезки объектов или любых других гадостей.

  • Массив std::auto_ptr выполнит эту работу, но тогда мне придется иметь дело с их инициализацией, поиском «свободного слота», отсутствием итераторов и т. д. Кажется немного безумным заново изобретать все это колесо.

  • boost::ptr_vector выглядел многообещающе, однако он ведет себя немного странно, поскольку при сборке в Linux ему требуется, чтобы ExampleBase не был чисто виртуальным - и я не понимаю, почему... Итак, boost::ptr_vector отсутствует.

Это кажется простой и, вероятно, действительно распространенной ситуацией. Итак, как лучше всего это сделать? Я открыт для любого другого стандартного или ускоренного способа сделать это: в зависимости от того, что «лучше».


person maxpenguin    schedule 26.09.2009    source источник
comment
Укажите сообщение об ошибке, которое вы получили для ptr_vector. Это определенно не должно требовать, чтобы базовый класс был неабстрактным - что-то еще не так.   -  person Pavel Minaev    schedule 26.09.2009
comment
Удаление объектов с помощью указателя типа ABC* (абстрактный базовый класс) просто не работает, если ABC не включает виртуальный деструктор. Ошибка компиляции, которую вы получаете, на самом деле защищает вас от неопределенного поведения.   -  person sellibitze    schedule 26.09.2009


Ответы (3)


boost::ptr_vector - это то, что вам нужно, если у вас есть Boost. Это должно работать для вашего сценария — если это не так, значит, что-то еще не так, поэтому опубликуйте сообщение об ошибке.

Кроме того, вы можете выбрать std::vector< boost::shared_ptr<ExampleBase> >. Это менее идеально, потому что подсчет ссылок добавит некоторые накладные расходы, особенно при изменении размера вектора, но в остальном рабочее решение.

Если ваша реализация поддерживает TR1 (последние версии g++ и MSVC), вместо этого вы можете использовать std::tr1::shared_ptr. На самом деле это может быть лучше, потому что реализацию STL можно оптимизировать на основе некоторых внутренних знаний — например, в MSVC std::vector знает, что он может использовать swap вместо конструктора копирования для std::tr1::shared_ptr, и делает именно это, избегая постоянных изменений счетчика ссылок.

person Pavel Minaev    schedule 26.09.2009

Есть несколько вариантов.

  • Используйте boost::ptr_vector в качестве контейнера. Он должен работать даже с абстрактным базовым классом. Просто убедитесь, что ваш базовый класс включает в себя виртуальный деструктор.
  • Используйте несколько контейнеров, которые напрямую управляют разными объектами производных классов, и создайте еще один, использующий только указатели. Это может быть громоздким, а также заставляет вас думать о том, когда/если указатели становятся недействительными. Но у него также есть то преимущество, что объекты одного и того же типа находятся близко друг к другу.
  • Используйте интеллектуальный указатель (например, указатель общего владения boost) и сохраните его в своем контейнере. C++0x предложит еще один хороший интеллектуальный указатель с меньшими накладными расходами, применимый, если вы не хотите разделять владение: unique_ptr. К сожалению, в C++03 нет хорошей эмуляции этого.
  • Используйте другую форму «дескриптора», объекта, который обертывает полиморфный объект и управляет им с помощью указателя с семантикой значения (это может быть еще один интеллектуальный указатель, который вы могли бы назвать «clone_ptr»).
person sellibitze    schedule 26.09.2009

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

http://www.boost.org/doc/libs/1_40_0/libs/ptr_container/doc/ptr_container.html

person Chris Bednarski    schedule 26.09.2009