D 结构复制构造函数

D struct copy constructor

是否可以像在 C++ 中那样显式调用结构复制构造函数?我可以这样写吗:

struct foo {
    void bar() {}
}

foo f;
foo(f).bar();

或者我总是需要为某些变量分配新值?

D 本身没有复制构造函数,但是您可以使用

使用现有构造函数的内容调用隐式构造函数(这至少会创建一个浅拷贝)
foo(f.tupleof).bar()

f.tupleof以适合自动扩展为函数参数列表的形式给出结构成员列表。

嗯,从技术上讲,D 甚至没有复制构造函数。相反,结构可以具有 postblit 构造函数。例如

struct S
{
    this(this)
    {
    }
}

一般来说,D尽量移动结构而不是复制它们。当它确实复制它们时,它会按位复制结构,然后 运行s postblit 构造函数(如果有的话)在事后改变结构来做一些需要做的事情,而不是按位复制 - 例如如果你想要一个成员的深拷贝

struct S
{
    this(this)
    {
        if(i !is null)
            i = new int(*i);
    }

    int* i;
}

另一方面,复制构造函数(在 C++ 中)构造一个新的 struct/class 并使用正在复制的 struct/class 中相应成员的副本初始化每个成员 - 或者使用其他任何东西它在复制构造函数的初始化列表中被初始化。它不会像 D 的 postblit 构造函数那样复制然后变异。因此,复制构造函数和 postblit 构造函数略有不同。

其中一个副作用是,虽然 C++ 中的所有 structs/class 都有复制构造函数(如果您不声明一个,编译器总是为您生成一个),但并非 D 中的所有结构都有 postblit构造函数。事实上,大多数人没有。如果该结构包含另一个具有 postblit 构造函数的结构,编译器将生成一个,否则,它不会生成一个,并且复制只是按位复制。而且,如果没有 postblit 构造,则不能隐式或显式调用它。

现在,如果我们编译这个

struct A
{
}
pragma(msg, "A: " ~ __traits(allMembers, A).stringof);

它打印

A: tuple()

A 没有成员——无论是成员变量还是函数。 None 已声明,编译器已生成 none.

struct B
{
    A a;
    string s;
}
pragma(msg, "B: " ~ __traits(allMembers, B).stringof);

打印

B: tuple("a", "s")

它有两个成员——显式声明的成员变量。它也没有任何功能。声明成员变量不是编译器生成任何函数的理由。但是,当我们编译

struct C
{
    this(this)
    {
        import std.stdio;
        writeln("C's postblit");
    }

    int i;
    string s;
}
pragma(msg, "C: " ~ __traits(allMembers, C).stringof);

它打印

C: tuple("__postblit", "i", "s", "__xpostblit", "opAssign")

不仅列出了它的两个成员变量,它还有__postblit(显式声明的postblit构造函数)以及__xpostblitopAssign__xpostblit 是由编译器生成的 postblit 构造函数(稍后会详细介绍),opAssign 是编译器生成的赋值运算符(这是必需的,因为 C 有一个 postblit构造函数)。

struct D
{
    C[5] sa;
}
pragma(msg, "D: " ~ __traits(allMembers, D).stringof);

打印

D: tuple("sa", "__xpostblit", "opAssign")

请注意它有 __xpostblit 但没有 __postblit。那是因为它没有明确声明的 postblit 构造函数。 __xpostblit 是为了调用每个成员变量的 postblit 构造函数而生成的。 saC 的静态数组,C 有一个 postblit 构造函数。因此,为了正确复制 saC 的 postblit 构造函数必须在 sa 中的每个元素上调用。 D__xpostblit 就是这样做的。 C 也有 __xpostblit,但它没有任何带有 postblit 构造函数的成员,所以它的 __xposblit 只是调用它的 __postblit.

struct E
{
    this(this)
    {
        import std.stdio;
        writeln("E's postblit");
    }

    C c;
}
pragma(msg, "E: " ~ __traits(allMembers, E).stringof);

打印

E: tuple("__postblit", "c", "__xpostblit", "opAssign")

所以,E - 就像 C - 有 __postblit__xpostblit__postblit是显式的postblit构造函数,__xpostblit是编译器生成的,但是,在这种情况下,struct实际上有带有postblit构造函数的成员变量,所以__xpostblit有更多要做的不仅仅是打电话 __postblit.

如果你有

void main()
{
    import std.stdio;
    C c;
    writeln("__posblit:");
    c.__postblit();
    writeln("__xposblit:");
    c.__xpostblit();
}

它会打印

__posblit:
C's postblit
__xposblit:
C's postblit

因此,两者之间没有真正的区别,而如果您有

void main()
{
    import std.stdio;
    D d;
    writeln("__xposblit:");
    d.__xpostblit();
}

它会打印

__xposblit:
C's postblit
C's postblit
C's postblit
C's postblit
C's postblit

请注意 C' postblit 被调用了 5 次 - D 的成员 sa 中的每个元素调用一次。而且我们无法在 D 上调用 __postblit,因为它没有显式的 postblit 构造函数 - 只是隐式的。

void main()
{
    import std.stdio;
    E e;
    writeln("__posblit:");
    e.__postblit();
    writeln("__xposblit:");
    e.__xpostblit();
}

会打印

__posblit:
E's postblit
__xposblit:
C's postblit
E's postblit

并且在这种情况下,我们可以看到 __postblit__xpostblit 是不同的。调用 __postblit 只是调用显式声明的 postblit 构造函数,而 __xpostblit 调用它和成员变量的 postblit 构造函数。

当然,由于 AB 没有 posblit 构造函数,也没有成员拥有它们,因此调用 __postblit__xpostblit 在他们身上。

所以,是的,您可以显式调用 postblit 构造函数 - 但前提是它有一个构造函数,而且您几乎肯定不应该调用它。如果一个函数以 __ 开头,或者它是重载运算符之一(因此以 op 开头),那么它几乎永远不应该被显式调用 - 这包括 postblit 构造函数。但是如果你确实找到了调用它的正当理由,请记住你可能想要调用 __xpostblit 而不是 __postblit,否则成员变量的 postblit 将不会是 运行.您可以通过执行 __traits(hasMember, S1, "__xpostblit") 或使用 std.traits 中命名错误的 hasElaborateCopyConstructor 来测试它(大多数代码应该使用 hasElaborateCopyConstructor,因为它更惯用)。如果你出于某种原因想调用 __postblit,你需要用 __traits 而不是 std.traits 来测试它,因为 d运行time 之外几乎没有任何东西关心类型是否声明为 __postblit。关心 posblit 构造函数的东西关心 __xpostblit,因为无论 __postblit 是否被声明,它都可能存在。