8

During a dive into dynamic memory, it occurred to me it appears contradictory how trivial types begin their lifetime. Consider the snippet

void* p = ::operator new(sizeof(int));  // 1
// 2
new (p) int; // 3

When does the int start its lifetime?

  1. Only acquires storage, ::operator new is specified to have the effect (from [new.delete.single])

    The allocation functions called by a new-expression to allocate size bytes of storage. [...] allocates storage suitably aligned to represent any object of that size provided the object's type does not have new-extended alignment.

    Given that acquiring storage is insufficient in creating an object, the int cannot have begin its lifetime here.

  2. At this point, suitbale storage for the int has been acquired.

  3. The int is created by placement new. But somehow its lifetime didn't begin here, since from [basic.life]

    [...] An object is said to have non-vacuous initialization if it is of a class or aggregate type and it or one of its subobjects is initialized by a constructor other than a trivial default constructor. The lifetime of an object of type T begins when:

    • storage with the proper alignment and size for type T is obtained, and

    • if the object has non-vacuous initialization, its initialization is complete [...]

    int is neither a class nor aggregate type, hence it has vacuous initialization. Therefore only the first bullet applies. However, this is clearly not when storage is obtained and therefore cannot be when its lifetime starts.

Some context

Allocators are required to return memory without constructing its elements. Yet this doesn't make sense with trivial types. The effects of a.allocate(n) with a an allocator object for type T is

Memory is allocated for n objects of type T but objects are not constructed.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Passer By
  • 19,325
  • 6
  • 49
  • 96
  • 2
    related [“constructing” a trivially-copyable object with memcpy](https://stackoverflow.com/q/30114397/1708801) there are [several linked questions](https://stackoverflow.com/questions/linked/30114397?sort=newest) that cover several variations of this theme one of them will almost surely cover this case. – Shafik Yaghmour Feb 28 '18 at 20:50
  • Relevant is [P0593](http://wg21.link/p0593) (revision 2 recently updated), although that hasn't gotten so far yet as to propose concrete changes to the standard – M.M Feb 28 '18 at 21:54
  • this isn't a dupe of the other threads (the placement-new is substantially different to `memcpy` or whatever) . The original title of this thread was misleading as to the real issue it raises – M.M Feb 28 '18 at 22:14
  • @M.M: But [the answer is the same](https://stackoverflow.com/a/40874245/734069). The placement `new` is what causes the object to be created, pursuant to [intro.object]/1. You cannot start the lifetime of an object that doesn't exist. – Nicol Bolas Feb 28 '18 at 22:54
  • @NicolBolas Nothing in the answer you linked applies to this question. In the code in this question there *is* an object created by placement-new; that answer (and the question it answers) is talking about the situation where there are no objects created – M.M Feb 28 '18 at 22:57

1 Answers1

7

Technically, the new-expression always obtains storage. The code new(p) int both obtains storage and creates an object, and according to [basic.life]/1, the object's lifetime began when new(p) int obtained the storage.

According to N4659 [expr.new], the code new(p) int generates a call to an allocation function ::operator new(sizeof(int), p). And under [new.delete.placement], the standard library defines such a function:

void* operator new(std::size_t size, void* ptr) noexcept;

Returns: ptr.

Remarks: Intentionally performs no other action.

Although "no other action" is performed, and probably the implementation will optimize out any actual function call, this call to an allocation function still counts as obtaining storage for the object being created by the new-expression.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • 1
    That's unexpected. I like how the standard knows how this looks totally bogus but is there to make the language consistent – Passer By Mar 01 '18 at 04:27
  • 1
    @PasserBy The standard would seem more consistent if [basic.life]/1 were completed by [intro.object]/1 in this answer: *[...]An object is created by a definition, by a new-expression, when implicitly changing the active member of a union, or when a temporary object is created* – Oliv Mar 01 '18 at 07:16
  • @Oliv When is a definition executed? – curiousguy Dec 13 '19 at 08:24
  • @curiousguy That is a trap no? I think before C++20 it was when the placement allocation function returns. But now, it is when the vacuous initialization of the int has been completed. – Oliv Dec 13 '19 at 13:35
  • This is outdated since C++20 is published. See also [P0593R6](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0593r6.html) (adopted 2020-02). – FrankHB Feb 01 '23 at 14:43
  • @FrankHB Is it a moot point? The `int` object created by `new(p) int` didn't exist before `new(p) int`, and does exist after `new(p) int`. Referring to the exact code in the question here. You could implicitly create an `int` object in the space before calling `new(p) int`, but that's a different object than the one `new(p) int` creates – M.M Feb 01 '23 at 20:22
  • @M.M In certain cases the _new-expression_ can be omitted and the effect is same. If there is such an _new-expression_, it has no effect besides returning a pointer value to the _sutiable created object_ as the result, creating _no_ object (because any object the result points to is considered already created _before_ evaluating the _new-expression_). This _is_ relavant to the context in the original question, albeit not necessarily about the code in the question. – FrankHB Feb 05 '23 at 21:03
  • To be specific, if the storage is provided by the same type of an suitable created object, there is no new object created in place, otherwise the effect of the pseudo destructor call will be unclear about which object to be destroyed. And at least for `int`, the pointer value of same address should only alias to the same object it pointed to. This is the practical basis of strict aliasing. The "implicitly creating object" wording is not very intuitive, though, but it is still clear about "creates and starts the lifetime of _zero_ or more objects of implicit-lifetime types" (emphasize mine). – FrankHB Feb 05 '23 at 21:24