я использую shared_ptr<Base>
для некоторого списка дерева с производными классами. Но я получаю нарушение указателя доступа, когда мое дерево разрушается.
Мой код выглядит примерно так, кроме того, это фактически напоминает мою ошибку во время выполнения:
#include <iostream>
#include <memory>
#include <vector>class Base;
typedef std::shared_ptr<Base> pBase;
class Derived;
class Base {
public:
std::vector<pBase> children;
pBase parent;
Base() {}
virtual ~Base() {}
virtual void doSomething() {}
void add(pBase i);
};
class Derived : public Base {
void doSomething() {
// Do something...
}
};
void Base::add(pBase i) {
i->parent = pBase(this);
children.push_back(i);
}int main() {
pBase tree = pBase(new Derived());
pBase child(new Derived());
child->add(pBase(new Derived()));
tree->add(child);
}
Также, когда я добавляю следующие строки в Base::~Base
:
станд :: соиЬ << «уничтожить» << название << станд :: епсИ;
И реализовать std :: string с именем name в Base
который отличается для каждого экземпляра, я вижу, что деструктор вызывается несколько раз (из-за Base::parent
Ссылка думаю). Это конечно вызвало мою ошибку, но я до сих пор не понимаю, почему это происходит, потому что shared_ptr<Base>
Ожидается, что подсчитать его ссылки, прежде чем на самом деле уничтожить его?
Я надеюсь, что некоторые могут сказать мне, что я делаю неправильно!
Но важнее то, как я могу это исправить!
Посмотрите на эту строку в add ()
i->parent = pBase(this);
Каждый раз, когда вы звоните добавить, вы создаете новый общий указатель на this
, Эти общие указатели являются отдельными — то есть они НЕ «поделился», как вы думаете. Итак, когда вы удаляете ребенка в первый раз, его родитель удаляется (потому что это общий указатель). Следовательно, ваш код взрывается.
Попробуйте (как начало) сделать родителя простым тупым указателем.
Base *parent;
Просто чтобы добавить к ответам других: канонический способ делать то, что вы хотите в очереди
i->parent = pBase(this);
это использовать std::enable_shared_from_this
. Вы
получать Base
от него
class Base : std::enable_shared_from_this<Base> {
Убедитесь, что каждый Base
Экземпляр принадлежит std::shared_ptr
, Это нормально в вашем случае, так как вы создаете объекты в выражениях, таких как
pBase child(new Derived());
использование shared_from_this()
вместо this
когда ты хочешь std::shared_ptr
, Проблемная линия тогда станет
i->parent = shared_from_this();
Вот
i->parent = pBase(this);
вы создаете умный указатель из простого старого указателя на объект, который вы не получили непосредственно от нового. Никогда не делай этого.
Как объяснил @Roddy, вы получаете отдельные объекты интеллектуальных указателей с отдельными счетчиками ссылок. Два счетчика ссылок для одного pointee не будут работать.
В вашем случае, вероятно, нормально сделать родительский указатель нормальным, как предложило @Roddy. Таким образом, у вас не будет проблем с циклическими ссылками. Просто убедитесь, что вы никогда не получите доступ к родительскому указателю после того, как удалили родительский указатель. Нет проблем, если вы удаляете всех потомков вместе с родителем (что происходит автоматически, если вы не храните более умные указатели на них где-то еще)
Если вы хотите инициализировать умный указатель, у вас есть два варианта: использовать умные указатели в каждом интерфейсе. К сожалению, это не работает для «этого», потому что это неявный параметр. Вам нужно будет передать умный указатель, который вы уже создали, методу вручную, в дополнительном параметре. Как это:
tree->add(tree, child);
Это немного уродливо, поэтому вы можете подумать о том, чтобы добавить «статический» метод, так что вам не нужно будет дважды передавать родительский элемент.
Другой вариант: используйте другой тип интеллектуального указателя, например boost :: intrusive_ptr, где вы можете хранить счетчик ссылок в pointee. Таким образом, вы сможете найти счетчик ссылок, даже если у вас есть только тупой указатель типа «это».
Изменить: ответ @jpalecek ниже, лучше. Используйте это. Себастьян.