D结构体的复制构造函数

4

是否可以显式调用结构体的复制构造函数,就像在C++中一样?我可以写类似这样的代码:

struct foo {
    void bar() {}
}

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

还是我总是需要给某个变量分配新值?

2个回答

10

实际上,D语言甚至没有拷贝构造函数。相反,结构体可以有后复制构造函数。例如:

struct S
{
    this(this)
    {
    }
}

D语言通常尽可能地移动结构体而不是复制它们。当必须复制时,它会进行结构体的位拷贝,然后运行postblit构造函数(如果有的话),以在位拷贝之后改变结构体以完成更多需要完成的任务 - 例如,如果您想要成员的深度复制。

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

    int* i;
}

在C ++中,复制构造函数会构造一个新的结构体/类,并用要复制的结构体/类中对应成员的副本或在复制构造函数的初始化器列表中初始化的内容来初始化每个成员。它不像D语言的后拷贝构造函数那样进行复制然后更改。因此,复制构造函数和后拷贝构造函数有微妙的区别。

其中一个副作用是,尽管C ++中的所有结构体/类都有复制构造函数(如果没有声明复制构造函数,则编译器始终会为您生成一个),但D语言中并非所有结构体都有后拷贝构造函数。实际上,大部分都没有。如果结构体包含具有后拷贝构造函数的另一个结构体,则编译器将生成一个后拷贝构造函数,否则,它将不会生成一个,并且复制只会进行位拷贝。如果没有后拷贝构造函数,则无法隐式或显式调用它。

现在,如果我们编译这个程序

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

它打印输出

A: tuple()

A没有成员 - 无论是成员变量还是函数。没有被声明,并且编译器也没有生成任何成员。

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(这是显式声明的后置构造函数),以及__xpostblitopAssign__xpostblit是编译器生成的后置构造函数(稍后会详细介绍),opAssign是赋值运算符,编译器也生成了它(因为C具有后置构造函数)。

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

打印

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

请注意,它具有__xpostblit但没有__postblit。这是因为它没有显式声明的后拷贝构造函数。生成了__xpostblit来调用每个成员变量的后拷贝构造函数。sa是一个C类型的静态数组,而C具有后拷贝构造函数。因此,为了正确地复制sa,必须在sa的每个元素上调用C的后拷贝构造函数。D__xpostblit就是这么做的。C也有__xpostblit,但它没有任何具有后拷贝构造函数的成员变量,因此它的__xpostblit只调用了它的__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 是显式的后置构造函数,而 __xpostblit 是编译器生成的构造函数。但是,在这种情况下,结构体实际上具有具有后置构造函数的成员变量,因此 __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的后复制函数被调用了5次 - 每个在D成员sa中的元素都会调用一次。而我们无法对D调用__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只调用显式声明的后复制构造函数,而__xpostblit则调用它和成员变量的后复制构造函数。

当然,由于AB没有后复制构造函数,也没有成员变量有后复制构造函数,因此在它们上调用__postblit__xpostblit将是非法的。

因此,是的,你可以显式调用后复制构造函数 - 但仅在它有一个时,并且你几乎肯定不应该调用它。如果一个函数以__开头,或者是其中一个重载运算符(因此以op开头),那么它几乎从不应该显式调用 - 包括后复制构造函数。但是,如果你确实找到了一个合法的理由来调用它,请记住你可能希望调用__xpostblit而不是__postblit,否则成员变量的后复制构造函数将不会运行。你可以通过执行__traits(hasMember, S1, "__xpostblit")或使用std.traits中糟糕命名的hasElaborateCopyConstructor来测试它(大多数代码应该使用hasElaborateCopyConstructor,因为它更符合惯用语法)。如果你想因为某种原因调用__postblit,你需要使用__traits进行测试,而不是std.traits,因为除了druntime外,几乎没有什么东西关心是否声明了__postblit类型。关心后复制构造函数的东西关心的是__xpostblit,因为它可以存在,无论是否声明了__postblit


2

D语言本身没有复制构造函数,但你可以通过以下方式使用隐式构造函数来复制现有对象的内容(至少会创建一个浅拷贝):

foo(f.tupleof).bar()
f.tupleof提供了结构体成员列表,适用于自动扩展到函数参数列表。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接