Совместное владение двойной бесплатной ошибкой

У меня есть класс Engine, который содержит и владеет некоторыми системами. Класс Engine имеет два контейнера, одну карту и один вектор. Оба хранят указатели на системы.

Шаблонная функция addSystem должна добавить в карту указатель на новую Систему нужного типа, а addToPipeline должна добавить указатель на Систему, переданную в качестве параметра в векторе. Я использовал для этого shared_ptrs, но я делаю что-то не так, потому что я получаю двойную бесплатную ошибку, если использую функцию addToPipeline.

Вот упрощенный класс Engine:

class Engine
{
public:

        template <class T>
        T& addSystem();

        void addToPipeline(System&);

private:
        std::map<std::type_index, std::shared_ptr<System>> m_systems;
        std::vector<std::shared_ptr<System>> m_pipeline;
};


void Engine::addToPipeline(System& sys)
{
        m_pipeline.push_back(std::shared_ptr<System>(&sys));
}


template <class T>
T& Engine::addSystem()
{
        std::shared_ptr<T> system = std::make_shared<T>();
        auto inserted = m_systems.emplace(typeid(T),system);
        return static_cast<T&>(*(*inserted.first).second);
}

Функции следует использовать, как показано ниже:

auto& POSITION_SYSTEM = engine.addSystem<PositionSystem>();
engine.addToPipeline(POSITION_SYSTEM);

Любая помощь приветствуется!


person Veritas    schedule 05.05.2014    source источник


Ответы (1)


В этой строке:

m_pipeline.push_back(std::shared_ptr<System>(&sys));

Вы создаете shared_ptr для уже управляемого объекта, так как вы уже обернули тот же объект в другой интеллектуальный указатель. Таким образом, вы получаете два счетчика ссылок для одного и того же объекта, таким образом, вы получаете двойное бесплатное использование.

Это не то, как следует использовать shared_ptr. Вместо этого вы должны вернуть shared_ptr из addSystem и взять один в качестве аргумента для addToPipeline:

void Engine::addToPipeline(std::shared_ptr<System> sys)
{
    m_pipeline.push_back(sys);
}


template <class T>
std::shared_ptr<T> Engine::addSystem()
{
    std::shared_ptr<T> system = std::make_shared<T>();
    m_systems.emplace(typeid(T),system);
    return system; // No need to use the return value of emplace
}

Идея с shared_ptrs заключается в том, что вместо использования голых указателей или ссылок вы всегда передаете shared_ptr (если только право собственности не имеет значения, тогда вы также можете передать ссылку). Вы должны сделать это таким образом, потому что счетчик ссылок управляется интеллектуальными указателями.

Редактировать: Как указала Розина: Конечно, вы все еще можете передавать ссылки на управляемый объект, если никто не пытается удалить соответствующий адрес. На самом деле это может быть предпочтительнее, если другой код заинтересован в использовании определенного объекта, но не беспокоится о владении им. Например, вы можете захотеть иметь общедоступный интерфейс, который позволяет получить ссылку на какой-либо объект, управляемый внутренним интеллектуальным указателем. Например:

class Foo {
public:
    Bar& getBar() {
        return *m_bar;
    }
private:
    std::shared_ptr<Bar> m_bar;
};

Это совершенно нормально, пока никто не делает delete &aFoo.getBar(), что происходит, если вы создаете новый shared_ptr с этой ссылкой, как вы это делали в исходном коде.

person lethal-guitar    schedule 05.05.2014
comment
Есть ли способ добиться того же, сохраняя ссылку в качестве возвращаемого типа? - person Veritas; 05.05.2014
comment
@Veritas, почему именно вам нужно вернуть ссылку? Я не могу придумать способ сделать это, не изменив каким-то образом ваш дизайн (например, иметь одну функцию, которая добавляет систему и добавляет ее в конвейер или что-то подобное) - person lethal-guitar; 05.05.2014
comment
@Veritas хорошо, за исключением того, что вы можете использовать навязчивый подход к подсчету ссылок, при котором все ваши объекты происходят от общего базового класса RefCounted или аналогичного, который хранит счетчик ссылок. Но тогда вам придется реализовать это самостоятельно или использовать Boost, STL не предоставляет такого механизма. - person lethal-guitar; 05.05.2014
comment
Я понимаю. Спасибо за быстрый ответ ! Я хотел сохранить ссылки, потому что так же используются и другие классы. Мне придется изменить интерфейс всех классов, если я изменю этот. - person Veritas; 05.05.2014
comment
@Veritas взгляните на это, если вы хотел бы использовать навязчивый подсчет ссылок Boost - person lethal-guitar; 05.05.2014
comment
@Veritas Вы все равно можете передать ссылку на свой объект, если какая-то функция хочет его использовать. Пока эта функция не вызывает для нее удаление. - person rozina; 05.05.2014
comment
@rozina Я не уверен, что понимаю, что ты имеешь в виду. - person Veritas; 05.05.2014