结构构造函数的奇怪行为

时间:2014-03-30 01:28:53

标签: pointers constructor d

我在D中编写了一个基本的Node结构,旨在用作树状结构的一部分。代码如下:

import std.algorithm: min;

alias Number = size_t;

struct Node {
    private {
        Node* left, right, parent;
        Number val;
    }

    this(Number n) {val = n;}

    this(ref Node u, ref Node v) {
       this.left = &u;
       this.right = &v;
       val = min(u.val, v.val);
       u.parent = &this;
       v.parent = &this;
    }

}

现在,我编写了一个简单的函数,它应该给我一个Node(意思是整个树),其中参数数组提供叶子,如下所示。

alias Number = size_t;

Node make_tree (Number[] nums) {
    if (nums.length == 1) {
         return Node(nums[0]);
    } else {
        Number half = nums.length/2;
        return Node(make_tree(nums[0..half]), make_tree(nums[half..$]));
    }
}

现在,当我尝试通过dmd运行它时,我收到以下错误消息:

Error: constructor Node.this (ulong n) is not callable using argument types (Node, Node)

这对我没有意义 - 为什么在给出两个参数时它会尝试调用一个参数的构造函数?

1 个答案:

答案 0 :(得分:3)

问题与构造函数无关。它与ref的传递有关。您尝试使用的构造函数

this(ref Node u, ref Node v) {...}

通过ref接受其参数。这意味着它们必须是左值(即可以在赋值的左侧)。但是你传递的是函数调用的结果,它不会被ref返回(所以,它返回一个临时的,这是一个rvalue - 可以在赋值的右边但不是左边)。所以,你要做的是非法的。现在,错误消息并不好,因为它给出了第一个构造函数而不是第二个构造函数的错误,但无论如何,您没有与您尝试执行的操作相匹配的构造函数。目前,我可以想到3个选项:

  1. 摆脱构造函数参数的ref。如果你只是传递函数调用的结果,就像你现在正在做的那样,让它接受ref无论如何都无法帮助你。返回的值将被移动到函数的参数中,因此不会进行任何复制,ref不会为您购买任何东西。当然,将返回值分配给局部变量,以便您可以将它们传递给构造函数,因为它当前正在编写失去一些东西,因为那样你就会制作不必要的副本。

  2. 重载构造函数,使其接受ref或non-ref。 e.g。

    void foo(ref Bar b) { ... }
    void foo(Bar b) { foo(b); } //this calls the other foo
    

    一般来说,当你有一个参数时,这种方法运行得相当好,但这里有点烦人,因为你添加参数时最终会出现指数级的函数签名爆炸。所以,对于你的构造函数,你最终会得到

    this(ref Node u, ref Node v) {...}
    this(ref Node u, Node v) { this(u, v); }
    this(Node u, ref Node v) { this(u, v); }
    this(Node u, Node v) { this(u, v); }
    

    如果你添加了第3个参数,你最终会得到 8个重载。因此,它实际上不会扩展到单个参数之外。

  3. 对构造函数进行模板化并使用auto ref。这基本上做了#2所做的,但你只需要编写一次函数:

    this()(auto ref Node u, auto ref Node v) {...}
    

    然后,这将生成一个函数的副本以匹配给定的参数(最多4个不同的版本,每个版本中包含完整的函数体,而不是其中3个只转发到第4个),但是你只需要写一次。在这种特殊情况下,将函数模板化可能是合理的,因为你正在处理一个结构。如果Node是一个类,它可能没有意义,因为模板化的函数不能是虚拟的。

  4. 所以,如果真的希望能够通过ref,那么在这种特殊情况下,您应该使用#3并对构造函数进行模板化并使用{{1 }}。不过,就个人而言,我不会打扰。我只想去#1。你的使用模式不会从auto ref得到任何东西,因为你总是传递两个右值,而你的auto ref结构不是很大,所以虽然你显然不想如果你不需要复制它,复制左值以将它传递给构造函数可能没那么重要,除非你做了 lot 。但同样,如果你传递一个左值,你只会得到一个副本,因为一个右值可以移动而不是复制,你现在只传递右值(至少用这里显示的代码) 。所以,除非你正在做一些不同的构造函数,它会涉及传递lvalues,所以没有必要担心左值 - 或者当节点从函数返回并传递给构造函数时被复制(因为那是一个移动,而不是副本)。因此,仅删除Node将是最佳选择。