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构造函数)以及__xpostblit
和opAssign
。 __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 构造函数而生成的。 sa
是 C
的静态数组,C
有一个 postblit 构造函数。因此,为了正确复制 sa
,C
的 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 构造函数。
当然,由于 A
和 B
没有 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
是否被声明,它都可能存在。
是否可以像在 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构造函数)以及__xpostblit
和opAssign
。 __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 构造函数而生成的。 sa
是 C
的静态数组,C
有一个 postblit 构造函数。因此,为了正确复制 sa
,C
的 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 构造函数。
当然,由于 A
和 B
没有 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
是否被声明,它都可能存在。