web-dev-qa-db-fra.com

Le compilateur C ++ permet une définition circulaire?

J'ai rencontré l'étrangeté suivante en faisant une erreur en écrivant du code pour les arbres. J'ai beaucoup dépouillé cet exemple, donc ce n'est qu'un arbre linéaire.

Fondamentalement, dans la fonction main (), je voulais attacher un Node à mon arbre, mais au lieu de l'attacher à "tree.root", je l'ai attaché à "root". Cependant , à ma grande surprise, non seulement tout s'est très bien compilé, mais j'ai pu appeler des méthodes sur les nœuds. Il n'a commis une erreur que lorsque j'ai essayé d'accéder à la variable membre "value".

Je suppose que ma principale question est la suivante: pourquoi le compilateur n'a-t-il pas détecté ce bogue?

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

Depuis "root" sur le RHS est une variable non déclarée à plat. Aussi, par curiosité, si le compilateur les laisse passer, les définitions circulaires ont-elles un cas d'utilisation réel? Voici le reste du code:

#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;
}

Production:

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

C'est un effet secondaire malheureux des définitions en C++, cette déclaration et définition se fait comme des étapes distinctes. Parce que les variables sont déclarées en premier, elles peuvent être utilisées dans leur propre initialisation:

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

Une fois la variable déclarée, elle peut être utilisée dans l'initialisation pour la définition complète d'elle-même.

Cela conduira à comportement indéfini dans AddLeaf si les données du deuxième argument sont utilisées, comme le la variable n'est pas initialisée.

23

Depuis "root" sur le RHS est une variable non déclarée à plat.

Ce n'est pas non déclaré. Il est déclaré par cette même déclaration. Cependant, rootis non initialisé au point où AddLeaf(root) est appelé, donc lorsque la valeur de l'objet est utilisée (par rapport à null etc.) dans la fonction , le comportement n'est pas défini.

Oui, l'utilisation d'une variable dans sa propre déclaration est autorisée, mais l'utilisation de sa valeur ne l'est pas. À peu près tout ce que vous pouvez en faire est de prendre l'adresse ou de créer une référence, ou des expressions qui ne traitent que du type de la sous-expression comme sizeof et alignof.

Oui, il existe des cas d'utilisation bien qu'ils puissent être rares. Par exemple, vous voudrez peut-être représenter un graphique, et vous pourriez avoir un constructeur pour nœud qui prend un pointeur vers un nœud lié comme argument et vous voudrez peut-être représenter un nœud qui se relie à lui-même. Ainsi, vous pourriez écrire Node n(&n). Je ne dirai pas si ce serait une bonne conception pour une API graphique.

13
eerorika