Какая привилегированная идиома С++ владеет коллекцией полиморфных объектов?

Рассмотрим следующие классы

class Base {
public:
    virtual void do_stuff() = 0;
};

class Derived : public Base {
public
    virtual void do_stuff() { std::cout << "I'm useful"; };
};

Теперь скажем, что я хочу иметь еще один класс, ответственный за владение объектами Base производных типов и перебирать их, называя их метод do_stuff(). Это похоже на это, но я не знаю, что T следует объявить как

class Owner {
public:
    void do_all_stuff() {
        //iterate through all items and call do_stuff() on them
    }

    void add_item(T item) {
        items.push_back(item);
    }

    vector<T> items;
}

Я вижу несколько возможностей:

T не может быть Base, так как я мог бы только добавлять объекты конкретного типа Base, так что не может быть и речи.

T может быть Base* или Base&, но теперь мне нужно доверять вызывающему устройству add_item(), чтобы передать мне указатель или ссылку на объект, который будет существовать, когда я его извлечу из items. Я не могу delete элементы в деструкторе Owner, так как я не знаю, что они были динамически распределены. Однако они должны быть delete 'd, если бы они были, что оставляет меня с двусмысленным владением.

T может быть Base* или Base&, и я добавляю метод Base* create_item<DerivedT>() { return new DerivedT; } к Owner. Таким образом, я знаю, что указатель останется в силе, и я его сохраню, но я не могу вызвать конструктор не по умолчанию на DerivedT. Кроме того, Owner становится ответственным за создание экземпляров объектов. Я также должен удалить каждый элемент в деструкторе Owner, хотя это и не проблема.

В принципе, я хотел бы сделать что-то похожее на:

Owner owner;

void add_one() {
    Derived d;

    owner.add_item(d);
}

void ready() {
    owner.do_all_stuff();
}

void main() {
    for(int i = 0; i < 10; ++i) {
        add_one();
    }
    ready();
}

Я уверен, что там что-то связано с семантикой перемещения (я мог перемещать объекты, переданные в add_items(), чтобы их владеть), но я до сих пор не могу понять, как будет объявлена ​​моя коллекция.

Что такое идиома С++ для такого рода полиморфного владения (в частности, с контейнерами STL)?

Ответы

Ответ 1

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

Умные указатели, такие как std::shared_ptr и std::unique_ptr, отлично работают в стандартных типах коллекций.

std::vector<std::unique_ptr<Base>>

Использование этого в Owner выглядит следующим образом:

class Owner {
public:
    void do_all_stuff() {
        //iterate through all items and call do_stuff() on them
    }

    void add_item(std::unique_ptr<Base> item) {
        items.push_back(std::move(item));
    }

    vector<std::unique_ptr<Base>> items;
}

Тип аргумента add_item определяет политику владения, необходимую для добавления элемента, и требует от пользователя выходить из своего пути, чтобы свернуть его. Например, они не могут случайно передать необработанный указатель с некоторой неявной, несовместимой семантикой собственности, потому что unique_ptr имеет явный конструктор.

unique_ptr также позаботится об удалении объектов, принадлежащих Owner. Хотя вам нужно убедиться, что Base имеет виртуальный деструктор. С вашим текущим определением вы получите поведение undefined. Полиморфные объекты всегда должны иметь виртуальный деструктор.

Ответ 2

Предполагая из вашего контекста, что Owner является единственным владельцем содержащихся объектов, T должен быть unique_ptr<Base> (где unique_ptr происходит от boost или std в зависимости от доступности С++ 11). Это правильно признает, что оно принадлежит только контейнеру и дополнительно отображает семантику передачи собственности в ваш вызов add_item.

Ответ 3

Другие альтернативы, заслуживающие рассмотрения, - использовать boost::ptr_container или, что еще лучше, использовать библиотеку типа adobe::poly или boost::type_erasure для ваших полиморфных типов, использовать полиморфизм во времени выполнения на основе стоимости - избегать необходимости в указателях, наследование и т.д.

Ответ 4

На самом деле вы не можете хранить ссылки в STL, только указатели или реальные значения. Таким образом, T является базой * Попробуйте другие вещи, которые вы будете жаловаться компилятору.