C ++ shared_ptr & lt; Base & gt; нарушение указателя доступа

я использую 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> Ожидается, что подсчитать его ссылки, прежде чем на самом деле уничтожить его?

Я надеюсь, что некоторые могут сказать мне, что я делаю неправильно!
Но важнее то, как я могу это исправить!

0

Решение

Посмотрите на эту строку в add ()

i->parent = pBase(this);

Каждый раз, когда вы звоните добавить, вы создаете новый общий указатель на this, Эти общие указатели являются отдельными — то есть они НЕ «поделился», как вы думаете. Итак, когда вы удаляете ребенка в первый раз, его родитель удаляется (потому что это общий указатель). Следовательно, ваш код взрывается.

Попробуйте (как начало) сделать родителя простым тупым указателем.

Base *parent;
3

Другие решения

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

i->parent = pBase(this);

это использовать std::enable_shared_from_this. Вы

  1. получать Base от него

    class Base : std::enable_shared_from_this<Base> {
    
  2. Убедитесь, что каждый Base Экземпляр принадлежит std::shared_ptr, Это нормально в вашем случае, так как вы создаете объекты в выражениях, таких как

    pBase child(new Derived());
    
  3. использование shared_from_this() вместо this когда ты хочешь std::shared_ptr, Проблемная линия тогда станет

    i->parent = shared_from_this();
    
3

Вот

i->parent = pBase(this);

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

Как объяснил @Roddy, вы получаете отдельные объекты интеллектуальных указателей с отдельными счетчиками ссылок. Два счетчика ссылок для одного pointee не будут работать.

В вашем случае, вероятно, нормально сделать родительский указатель нормальным, как предложило @Roddy. Таким образом, у вас не будет проблем с циклическими ссылками. Просто убедитесь, что вы никогда не получите доступ к родительскому указателю после того, как удалили родительский указатель. Нет проблем, если вы удаляете всех потомков вместе с родителем (что происходит автоматически, если вы не храните более умные указатели на них где-то еще)

Если вы хотите инициализировать умный указатель, у вас есть два варианта: использовать умные указатели в каждом интерфейсе. К сожалению, это не работает для «этого», потому что это неявный параметр. Вам нужно будет передать умный указатель, который вы уже создали, методу вручную, в дополнительном параметре. Как это:

tree->add(tree, child);

Это немного уродливо, поэтому вы можете подумать о том, чтобы добавить «статический» метод, так что вам не нужно будет дважды передавать родительский элемент.

Другой вариант: используйте другой тип интеллектуального указателя, например boost :: intrusive_ptr, где вы можете хранить счетчик ссылок в pointee. Таким образом, вы сможете найти счетчик ссылок, даже если у вас есть только тупой указатель типа «это».

Изменить: ответ @jpalecek ниже, лучше. Используйте это. Себастьян.

2