Возвращение разрывов ссылки на общий указатель вне методов класса - PullRequest
0 голосов
/ 15 ноября 2018

У меня есть дерево в C ++ и метод в дереве, который возвращает ссылку shared_ptr на новый лист всякий раз, когда этот лист добавляется в дерево. Когда я использую это внутри других методов класса Tree, он работает нормально, но когда я пытаюсь вызвать его из main, он падает, несмотря на то, что код, по-видимому, делает то же самое.

Вот классы Node и Tree:

#include <iostream>
#include <vector>
#include <memory>

class Node
{
public:
    int value;
    std::vector<std::shared_ptr<Node> > children; 
    Node(int value): value{value} {}
};

class Tree
{
public:
    std::shared_ptr<Node> root;
    Tree(): root {nullptr} {}

    std::shared_ptr<Node> CreateLeaf(int value)
    {
        return std::make_shared<Node>(value);
    }

    std::shared_ptr<Node>& AddLeaf(int value, std::shared_ptr<Node>& ptr)
    {
        if(ptr == nullptr)
        {
            ptr = std::move(CreateLeaf(value));
            return ptr;
        }
        else
        {
            std::shared_ptr<Node> newLeaf = CreateLeaf(value);
            ptr->children.push_back(std::move(newLeaf));
            return ptr->children.back();
        }
    }

    void otherMethod()
    {
        AddLeaf(1, root);
        std::shared_ptr<Node>& temporary = AddLeaf(2, root);
        std::cout << "temporary->value: " << temporary->value << std::endl;
    }

};

Если основная функция:

int main()
{
    Tree t;
    t.otherMethod();
}

Тогда программа работает правильно.

Однако, если основная функция:

int main()
{
    Tree t;
    t.AddLeaf(1, t.root);
    std::shared_ptr<Node>& b = t.AddLeaf(2, t.root);
    std::cout << "b->value = " << b->value << std::endl; 

}

программа вылетает, несмотря на то, что она делает то же самое. Кажется, что AddLeaf просто хранит nullptr в b, несмотря на то, что он является ссылкой на постоянный объект, t.root-> children [0]. Почему он это делает?

1 Ответ

0 голосов
/ 15 ноября 2018

Наличие ссылок на элементы в векторе или на любой саморазменяющийся контейнер опасно. Я имел дело с этим, когда создавал свою первую игру на C ++.

По сути, то, что происходит:

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

Передача ссылок на shared_ptr в векторе способствует неопределенному поведению. Разумнее было бы просто вернуть новый shared_ptr и увеличить счетчик ссылок.
Таким образом, когда вектор изменяется, ни одна ссылка в вашей программе не становится недействительной.

Удаление ссылки должно помочь:

std::shared_ptr<Node> AddLeaf(int value, std::shared_ptr<Node>& ptr)
{
    if(ptr == nullptr)
    {
        ptr = std::move(CreateLeaf(value));
        return ptr;
    }
    else
    {
        std::shared_ptr<Node> newLeaf = CreateLeaf(value);
        ptr->children.push_back(std::move(newLeaf));
        return ptr->children.back();
    }
}

В любом случае, поскольку я вижу, что вы создаете дерево, возможно, вы хотели бы взглянуть на код, который я написал в (заброшенном) проекте: https://github.com/wvanbreukelen/LexMe/blob/feature-tree-node-vector-specialization/LexMe/TreeNode.h
It's довольно полная реализация универсального дерева с дружелюбием к семантике перемещения и итераторам.

...