C++编译器允许循环定义?

Par*_*dox 22 c++ c++14

在为树写一些代码时犯了一个错误,我遇到了以下奇怪的问题.我已经删除了很多这个例子,所以它只是一个线性树.

基本上,在main()函数中,我想将一个Node附加到我的树上,但是我没有将它附加到"tree.root",而是将它附加到"root".但是,令我惊讶的是,它不仅编译得很好,而且我能够在节点上调用方法.它只在我尝试访问"value"成员变量时出错.

我想我的主要问题是,为什么编译器没有抓到这个bug?

std::shared_ptr<Node> root = tree.AddLeaf(12, root);
Run Code Online (Sandbox Code Playgroud)

由于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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

root->SubtreeDepth() = 2
child->SubtreeDepth() = 1
Run Code Online (Sandbox Code Playgroud)

Som*_*ude 23

这是C++中定义的一个不幸的副作用,声明和定义是作为单独的步骤完成的.因为变量是先声明的,所以可以在自己的初始化中使用它们:

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

声明变量后,可以在初始化中使用它来完成自身的定义.

这将导致未定义的行为AddLeaf,如果使用第二个参数的数据,作为变量未初始化.

  • @UKMonkey请注意,这不是赋值,而是复制构造(或者更确切地说是[复制初始化](https://en.cppreference.com/w/cpp/language/copy_initialization)).该定义等于`std :: shared_ptr <Node> root(tree.AddLeaf(12,root));`将不会调用默认构造函数. (8认同)
  • 如果变量是*read*from或***,它只会导致未定义的行为.如果存储了其地址或标识但从未读取或写入变量的值,则不会发生UB.然而,它非常脆弱. (7认同)
  • @UKMonkey:编译器别无选择.标准严格定义构造函数的重载决策,并且单独的参数数量排除了默认构造函数.效率不是其中的一个因素. (4认同)
  • 至少在C中(虽然除了与c的原始兼容性之外,在c ++中没有想到任何好的理由)但是有充分的理由分别进行定义和声明:`Foo*foo = malloc(sizeof(*foo));`是一个常见的习惯用法,避免在重构代码时常见的陷阱. (3认同)

eer*_*ika 13

由于RHS上的"根"是一个平坦的未声明变量.

这不是未宣布的.它是由同一个声明宣布的.然而,root 在其中点未初始化AddLeaf(root)被调用,所以当使用对象的值(相对于空等)的函数中,该行为是未定义.

是的,允许在自己的声明中使用变量,但使用其值不是.几乎所有你可以用它来做的是获取地址或创建一个引用,或只处理子表达式类型的表达式,如sizeofalignof.

是的,有用例虽然可能很少见.例如,您可能希望表示一个图形,并且您可能有一个节点构造函数,它将指向链接节点的指针作为参数,您可能希望能够表示与其自身链接的节点.因此你可以写Node n(&n).我不会争论这对于图形API是否是一个好的设计.