std :: shared_ptr этого

101

Сейчас я пытаюсь научиться использовать умные указатели. Однако при проведении некоторых экспериментов я обнаружил следующую ситуацию, для которой не смог найти удовлетворительного решения:

Представьте, что у вас есть объект класса A, являющийся родительским для объекта класса B (дочерний), но оба должны знать друг друга:

class A;
class B;

class A
{
public:
    void addChild(std::shared_ptr<B> child)
    {
        children->push_back(child);

        // How to do pass the pointer correctly?
        // child->setParent(this);  // wrong
        //                  ^^^^
    }

private:        
    std::list<std::shared_ptr<B>> children;
};

class B
{
public:
    setParent(std::shared_ptr<A> parent)
    {
        this->parent = parent;
    };

private:
    std::shared_ptr<A> parent;
};

Вопрос в том, как объект класса A может передать a std::shared_ptrсамого себя ( this) своему потомку?

Существуют решения для общих указателей Boost ( получение boost::shared_ptrforthis ), но как с этим справиться с помощью std::интеллектуальных указателей?

Икар
источник
2
Как и любой другой инструмент, вы должны использовать его, когда это необходимо. Использование смарт - указатели на то , что вы делаете , это не
YePhIcK
Аналогично бусту. Смотрите здесь .
juanchopanza
1
Это проблема на этом уровне абстракции. Вы даже не знаете, что «это» указывает на память в куче.
Vaughn Cato
Ну, в языке нет, но у вас есть. Пока вы отслеживаете, что где, все будет в порядке.
Alex

Ответы:

168

Есть как std::enable_shared_from_thisраз для этого. Вы наследуете его и можете вызывать .shared_from_this()изнутри класса. Кроме того, вы создаете здесь циклические зависимости, которые могут привести к утечкам ресурсов. Это можно решить с помощью std::weak_ptr. Итак, ваш код может выглядеть так (при условии, что дети полагаются на существование родителя, а не наоборот):

class A;
class B;

class A
    : public std::enable_shared_from_this<A>
{
public:
    void addChild(std::shared_ptr<B> child)
    {
        children.push_back(child);

        // like this
        child->setParent(shared_from_this());  // ok
        //               ^^^^^^^^^^^^^^^^^^
    }

private:     
    // note weak_ptr   
    std::list<std::weak_ptr<B>> children;
    //             ^^^^^^^^
};

class B
{
public:
    void setParent(std::shared_ptr<A> parent)
    {
        this->parent = parent;
    }

private:
    std::shared_ptr<A> parent;
};

Однако обратите внимание, что для вызова .shared_from_this()требуется, чтобы thisон принадлежал пользователю std::shared_ptrв момент вызова. Это означает, что вы больше не можете создавать такой объект в стеке и, как правило, не можете вызывать .shared_from_this()из конструктора или деструктора.

Юрий Килочек
источник
1
Спасибо за ваше объяснение и за указание на мою проблему круговой зависимости.
Icarus
@ Дедупликатор, что ты имеешь в виду?
юрий килочек
Попробуйте построить на shared_ptrоснове конструкции по умолчанию shared_ptrи на что бы вы ни
указали
1
@Deduplicator - это, простите за каламбур, довольно бессмысленный общий указатель. Этот конструктор предназначен для использования с указателями на элементы управляемого объекта или его баз. В любом случае, о чем вы (извините)? Эти не владеющие shared_ptrне имеют отношения к этому вопросу. shared_from_thisВ предварительных условиях четко указано, что объект должен принадлежать (а не просто shared_ptrуказываться ) кем-то в момент вызова.
юрий килочек
1
@kazarey Право собственности на объект shared_ptrтребуется в момент вызова, но в типичном шаблоне использования, то есть чем-то вроде shared_ptr<Foo> p(new Foo());, shared_ptrпредполагает владение объектом только после того, как он полностью построен. Это можно обойти, создав shared_ptrконструктор, инициализированный с помощью, thisи сохранив его где-то нелокально (например, в ссылочном аргументе), чтобы он не умер после завершения конструктора. Но такой запутанный сценарий вряд ли понадобится.
юрий килочек 06
9

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

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

Кроме того, вы возвращаете слабый указатель в getChild(). Тем самым вы заявляете, что вызывающий абонент не должен заботиться о праве собственности. Теперь это может быть очень ограничивающим, но также при этом вы должны убедиться, что рассматриваемый дочерний элемент не будет уничтожен, пока все еще сохраняются какие-либо слабые указатели, если вы будете использовать интеллектуальный указатель, он будет отсортирован сам .

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

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