Компилятор C ++ допускает круговое определение? - PullRequest
0 голосов
/ 07 января 2019

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

По сути, в функции main () я хотел присоединить Node к своему дереву, но вместо того, чтобы прикреплять его к «tree.root», я прикрепил его просто к «root». Однако, к моему удивлению, все это не только прекрасно скомпилировалось, но Я смог вызывать методы на узлах . Он только допустил ошибку, когда я попытался получить доступ к переменной-члену "value".

Наверное, мой главный вопрос: почему компилятор не уловил эту ошибку?

std::shared_ptr<Node> root = tree.AddLeaf(12, root);

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

#include <iostream>
#include <memory>

struct Node
{
    int value;
    std::shared_ptr<Node> child;

    Node(int value)
    : value {value}, child {nullptr} {}

    int SubtreeDepth()
    {
        int current_depth = 1;
        if(child != nullptr) return current_depth + child->SubtreeDepth();
        return current_depth;
    }
};

struct Tree
{
    std::shared_ptr<Node> root;

    std::shared_ptr<Node> AddLeaf(int value, std::shared_ptr<Node>& ptr)
    {
        if(ptr == nullptr)
        {
            ptr = std::move(std::make_shared<Node>(value));
            return ptr;
        }
        else
        {
            std::shared_ptr<Node> newLeaf = std::make_shared<Node>(value);
            ptr->child = std::move(newLeaf);
            return ptr->child;
        }
    }
};


int main(int argc, char * argv[])
{

    Tree tree;
    std::shared_ptr<Node> root = tree.AddLeaf(12, root);
    std::shared_ptr<Node> child = tree.AddLeaf(16, root);

    std::cout << "root->SubtreeDepth() = " << root->SubtreeDepth() << std::endl; 
    std::cout << "child->SubtreeDepth() = " << child->SubtreeDepth() << std::endl; 

    return 0;
}

Выход:

root->SubtreeDepth() = 2
child->SubtreeDepth() = 1

Ответы [ 2 ]

0 голосов
/ 07 января 2019

Так как «корень» в RHS является неопределенной необъявленной переменной.

Это не объявлено. Об этом говорится в том же заявлении. Однако root является неинициализированным в точке, где вызывается AddLeaf(root), поэтому при использовании значения объекта (по сравнению с нулевым значением и т. Д.) Внутри функции поведение не определено.

Да, использование переменной в ее собственном объявлении разрешено, но использование ее значения запрещено. Практически все, что вы можете с этим сделать - это взять адрес или создать ссылку, или выражения, которые имеют дело только с типом подвыражения, например sizeof и alignof.

Да, есть варианты использования, хотя они могут быть редкими. Например, вы можете представлять граф, и у вас может быть конструктор для узла, который принимает указатель на связанный узел в качестве аргумента, и вы можете захотеть представлять узел, который связывается с самим собой. Таким образом, вы можете написать Node n(&n). Я не буду спорить, будет ли это хорошим дизайном для графического API.

0 голосов
/ 07 января 2019

Это неприятный побочный эффект определений в C ++, это объявление и определение выполняются как отдельные шаги. Поскольку переменные объявлены первыми, они могут использоваться для собственной инициализации:

std::shared_ptr<Node> root = tree.AddLeaf(12, root);
^^^^^^^^^^^^^^^^^^^^^^^^^^   ^^^^^^^^^^^^^^^^^^^^^^
Declaration of the variable  Initialization clause of variable

Как только переменная объявлена, ее можно использовать при инициализации для полного определения самой себя.

Это приведет к неопределенному поведению в AddLeaf, если используются данные второго аргумента, поскольку переменная не инициализирована.

...