Constructing a class in-place
Petar
Petar
Thu Jul 26 21:22:45 UTC 2018
On Thursday, 26 July 2018 at 12:45:52 UTC, Johan Engelen wrote:
> On Wednesday, 25 July 2018 at 08:11:59 UTC, rikki cattermole
> wrote:
>>
>> Standard solution[0].
>>
>> [0] https://dlang.org/phobos/std_conv.html#.emplace.4
>
> Thanks for pointing to D's placement new. This is bad news for
> my devirtualization work; before, I thought D is in a better
> situation than C++, but now it seems we may be worse off.
>
> Before I continue the work, I'll have to look closer at this
> (perhaps write an article about the situation in D, so more ppl
> can help and see what is going on). In short:
> C++'s placement new can change the dynamic type of an object,
> which is problematic for devirtualization. However, in C++ the
> pointer passed to placement new may not be used afterwards
> (it'd be UB). This means that the code `A* a = new A();
> a->foo(); a->foo();` is guaranteed to call the same function
> `A::foo` twice, because if the first call to `foo` would do a
> placement new on `a` (e.g. through `this`), the second call
> would be UB.
> In D, we don't have placement new, great! And now, I learn that
> the _standard library_ _does_ have something that looks like
> placement new, but without extra guarantees of the spec that
> C++ has.
> For some more info:
> https://stackoverflow.com/a/49569305
> https://stackoverflow.com/a/48164192
>
> - Johan
Please excuse if my question is too naive, but how does this
change anything?
The general pattern of using classes is:
1. Allocate memory. This can be either:
1.a) implicit dynamic heap allocation done by the call to
`GC.malloc` invoked via the implementation of the `new` operator
for classes.
1.b) explicit dynamic heap allocation via any allocator
(`GC.malloc`, libc, std.experimental.allocator, etc.)
(1.b) is also a special case for class created via `new` - COM
classes are allocated via malloc - see:
https://github.com/dlang/druntime/blob/cb5efa9854775c5a72acd6870083b16e5ebba369/src/rt/lifetime.d#L79)
1.c) implicit stack allocation via `scope c = new Class();`
1.d) implicit stack allocation via struct wrapper like `auto c
= scoped!Class();`
1.e) explicit stack allocation via
`void[__traits(classInstanceSize, A)] buf = void;`
1.f) explicit stack allocation via `void[] buf =
alloca(__traits(classInstanceSize, A))[0 ..
__traits(classInstanceSize, A)];`
1.g) static allocation as thread-local or global variable or a
part of one via implace buffer. To be honest I'm not sure how
compilers implement this today.
1.e) Or any of the many variations of the above.
2. Explicit or implicit initialization its vtable, monitor (if
the class is or derived from Object) and its fields: `buf[] =
typeid(Class).initializer[];`
3. The class constructor is invoked, which in turn may require
calls to one more base classes.
...
4. The class is destroyed
4.a) Implicitly via the GC
4.b) Explicitly via `core.memory.__delete()`
4.b) Explicitly via `destroy()`
4.c) Explicitly via `std.experimental.allocator.dispose`, or
any similar allocator wrapper.
5. The class instance memory may be freed.
At the end of the day, the destructor is called and potentially
the memory is freed (e.g. if it's dynamically allocated). Nothing
stops the same bytes from being reused for another object of a
different type.
<slightly-off-topic>
C++ has the two liberties that D does not have or should/needs to
have:
A. The C++ standard is very hand-wavy about the abstract machine
on which C++ programs are semantically executing giving special
powers to its standard library to implement features that can't
be expressed with standard C++.
B. Its primary target audience of expert only programmers can
tolerate the extremely dense minefield of undefined behavior that
the standard committee doesn't shy from from putting behind each
corner in the name of easier development of 'sufficiently smart
compilers'. I'm talking about things like
https://en.cppreference.com/w/cpp/utility/launder which most C
programmers (curiously, 'C != C++') would consider truly bjorked.
</slightly-off-topic>
D on the other hand is (or at least I'm hopeful that it is)
moving away giving magical powers to its runtime or standard
library and is its embracing the spirit of bare bones systems
programming where the programmer is allowed or even encouraged to
implement everything from scratch (cref -betterC) for when that
is the most sensible option.
While C and C++ approach portability by abstracting the machine,
the approaches portability by laying all the cards on the table
and defining things, rather than letting them be unspecified or
at least documenting the implementation definition.
What I'm trying to say is that 'new' is not as special in D as it
is in C++ (ironically, as the 'new'-ed objects are GC-ed in D,
and what could be more magical in a language spec than a GC) and
given the ongoing @nogc long-term campaign its use is even
becoming discouraged.
Given this trend, the abundance of templates, increasing
availability of LTO and library-defined allocation and
object/resource management schemes I think it's more and more
likely that compilers will be see the full picture of class
lifetime and should either treat 1, 2, 3, 4 and 5 with C
semantics (don't make any assumptions) or try to detect instances
of 4 and 5 and mark the end of the object's lifetime in the
compiler to allow aliasing of its storage as a potentially
different type.
More information about the Digitalmars-d
mailing list