Is it possible to call struct copy constructor explicitly, like in C++? Can I write something like this:
struct foo {
void bar() {}
}
foo f;
foo(f).bar();
Or I always need to assign new value to some varialbe?
Well, technically, D doesn't even have copy constructors. Rather, structs can have postblit constructors. e.g.
struct S
{
this(this)
{
}
}
In general, D tries to move structs as much as possible and not copy them. And when it does copy them, it does a bitwise copy of the struct and then runs the postblit constructor (if there is one) to mutate the struct after the fact to do stuff that needs to be done beyond a bitwise copy - e.g. if you want a deep copy of a member
struct S
{
this(this)
{
if(i !is null)
i = new int(*i);
}
int* i;
}
A copy constructor (in C++), on the other hand, constructs a new struct/class and initializes each member with a copy of the corresponding member in the struct/class being copied - or with whatever it's initialized with in the copy constructor's initializer list. It doesn't copy and then mutate like happens with D's postblit constructor. So, a copy constructor and a postblit constructor are subtly different.
One of the side effects of this is that while all structs/class in C++ have copy constructors (the compiler always generates one for you if you don't declare one), not all structs in D have postblit constructors. In fact, most don't. The compiler will generate one if the struct contains another struct that has a postblit constructor, but otherwise, it's not going to generate one, and copying is just going to do a bitwise copy. And, if there is no postblit construct, you can't call it implicitly or explicitly.
Now, if we compile this
struct A
{
}
pragma(msg, "A: " ~ __traits(allMembers, A).stringof);
it prints
A: tuple()
A
has no members - be it member variables or functions. None have been declared, and the compiler has generated none.
struct B
{
A a;
string s;
}
pragma(msg, "B: " ~ __traits(allMembers, B).stringof);
prints
B: tuple("a", "s")
It has two members - the explicitly declared member variables. It doesn't have any functions either. Declaring member variables is not a reason for the compiler to generate any functions. However, when we compile
struct C
{
this(this)
{
import std.stdio;
writeln("C's postblit");
}
int i;
string s;
}
pragma(msg, "C: " ~ __traits(allMembers, C).stringof);
it prints
C: tuple("__postblit", "i", "s", "__xpostblit", "opAssign")
Not only are its two member variables listed, but it also has __postblit
(which is the explicitly declared postblit constructor) as well as __xpostblit
and opAssign
. __xpostblit
is the postblit constructor generated by the compiler (more on that in a second), and opAssign
is the assignment operator which the compiler generated (which is needed, because C
has a postblit constructor).
struct D
{
C[5] sa;
}
pragma(msg, "D: " ~ __traits(allMembers, D).stringof);
prints
D: tuple("sa", "__xpostblit", "opAssign")
Note that it has __xpostblit
but not __postblit
. That's because it does not have an explitly declared postblit constructor. __xpostblit
was generated to call the postblit constructor of each of the member variables. sa
is a static array of C
's, and C
has a postblit constructor. So, in order to copy sa
properly, the postblit constructor for C
must be called on each of the elements in sa
. D
's __xpostblit
does that. C
also has __xpostblit
, but it doesn't have any members with postblit constructors, so its __xposblit
just calls its __postblit
.
struct E
{
this(this)
{
import std.stdio;
writeln("E's postblit");
}
C c;
}
pragma(msg, "E: " ~ __traits(allMembers, E).stringof);
prints
E: tuple("__postblit", "c", "__xpostblit", "opAssign")
So, E
- like C
- has both __postblit
and __xpostblit
. __postblit
is the explicit postblit constructor, and __xpostblit
is the one generated by the compiler, However, in this case, the struct actually has member variables with a postblit constructor, so __xpostblit
has more to do than just call __postblit
.
If you had
void main()
{
import std.stdio;
C c;
writeln("__posblit:");
c.__postblit();
writeln("__xposblit:");
c.__xpostblit();
}
it would print
__posblit:
C's postblit
__xposblit:
C's postblit
So, there's no real difference between the two, whereas if you had
void main()
{
import std.stdio;
D d;
writeln("__xposblit:");
d.__xpostblit();
}
it would print
__xposblit:
C's postblit
C's postblit
C's postblit
C's postblit
C's postblit
Notice that C
' postblit gets called 5 times - once for each element in D
's member, sa
. And we couldn't call __postblit
on D
, because it doesn't have an explicit postblit constructor -
just the implicit one.
void main()
{
import std.stdio;
E e;
writeln("__posblit:");
e.__postblit();
writeln("__xposblit:");
e.__xpostblit();
}
would print
__posblit:
E's postblit
__xposblit:
C's postblit
E's postblit
And in this case, we can see that __postblit
and __xpostblit
are different. Calling __postblit
just calls the explicitly declared postblit constructor, whereas __xpostblit
calls it and the postblit constructors of the member variables.
And of course, since, A
and B
don't have posblit constructors and no members that have them, it would be illegal to call either __postblit
or __xpostblit
on them.
So, yes, you can call the postblit constructor explicitly - but only if it has one, and you almost certainly shouldn't be calling it. If a function starts with __
, or it's one of the overloaded operators (and thus starts with op
), then it almost never should be called explicitly - and that includes the postblit constructor. But if you do find a legitimate reason to call it, remember that you're probably going to want to call __xpostblit
rather than __postblit
, otherwise the postblit for the member variables won't be run. You can test for it by doing __traits(hasMember, S1, "__xpostblit")
or by using the badly named hasElaborateCopyConstructor
from std.traits (most code should use hasElaborateCopyConstructor
, since it's more idiomatic). If you want to call __postblit
for some reason, you'll need to test for it with __traits
rather than std.traits though, because pretty much nothing outside of druntime cares whether a type declared __postblit
. The stuff that does care about posblit constructors cares about __xpostblit
, since that can exist whether __postblit
was declared or not.
D doesn't have copy constructors per se, but you could call the implicit constructor with the contents of the existing one (which would create a shallow copy at least) with
foo(f.tupleof).bar()
The f.tupleof
gives the list of struct members in a form that is suitable for automatic expansion to a function argument list.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With