20

I know that this question was asked several times already but I couldn't find an answer for this particular case.

Let's say I have a trivial class that doesn't own any resources and has empty destructor and default constructor. It has a handful of member variables with in-class initialization; not one of them is const.

I want to re-initialize and object of such class it without writing deInit method by hand. Is it safe to do it like this?

void A::deInit()
{
  new (this)A{};
}

I can't see any problem with it - object is always in valid state, this still points to the same address; but it's C++ so I want to be sure.

Amomum
  • 6,217
  • 8
  • 34
  • 62
  • Are any of the member variables non-trivial? – Stephen Newell Oct 29 '19 at 12:42
  • _"Let's say I have a trivial class"_ @StephenNewell – YSC Oct 29 '19 at 12:44
  • 2
    Are any members of the object const? – NathanOliver Oct 29 '19 at 12:46
  • 2
    If this is valid, would it be equivalent to `*this = A{};`? – Kevin Oct 29 '19 at 12:46
  • @StephenNewell I'm also interested in what if it's not. I'm aware that owning resources will create a leak and that non of the members (or their members and so on) shall be const. But should a class be trivial in a sense of `std::is_trivial` (i.e. no virtual base classes)? – Amomum Oct 29 '19 at 12:48
  • @NathanOliver "It has a handful of member variables with in-class initialization; not one of them is const." – Amomum Oct 29 '19 at 12:48
  • @Kevin hmm, I read `*this = A{};` as 'create a temporary local object and assign its address to this' which doesn't sound correct. – Amomum Oct 29 '19 at 12:50
  • 2
    @Amomum `*this = A{};` means, `this->operator=(A{});`, i.e. create a temporary object and assign it to `*this`, replacing the values of all data members with the temporary's values. Since that's what you want and is (in my opinion) more readable than a placement new, I'd go with that instead. – Kevin Oct 29 '19 at 12:51
  • Looks safe (with all the caveats), but seems bad practice because it is brittle as A evolves over time (due to violating the caveats). – Eljay Oct 29 '19 at 12:52
  • 1
    @Kevin oh, my bad, you're right. Than - I _think_ it should be equal if copy is elided? – Amomum Oct 29 '19 at 12:53
  • 1
    instead of explaining the class in words, it is much better to write full class, and not only one method. see http://sscce.org/ – BЈовић Oct 29 '19 at 13:49

2 Answers2

17

Similarly to the legality of delete this, placement new to this is also allowed as far as I know. Also, regarding whether this, or other pre-existing pointers / references can be used afterwards, there are a few restrictions:

[basic.life]

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

  • the storage for the new object exactly overlays the storage location which the original object occupied, and
  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
  • the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
  • neither the original object nor the new object is a potentially-overlapping subobject ([intro.object]).

The first two are satisfied in this example, but the last two will need to be taken into consideration.

Regarding the third point, given that the function is non-const-qualified, it should be fairly safe to assume that the original object is non-const. The fault is on the caller side if the constness has been cast away. Regarding const / reference member, I think that can be checked by asserting that this is assignable:

static_assert(std::is_trivial_v<A> && std::is_copy_assignable_v<A>);

Of course, since assignability is a requirement, you could instead simply use *this = {}; which I would expect to produce the same program. A perhaps more interesting use case might be to reuse memory of *this for an object of another type (which would fail the requirements for using this, at least without reinterpreting + laundering).

Similar to delete this, placement new to this could hardly be described as "safe".

magkiller
  • 9
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Interesting. Is it possible to make sure all of this conditions are satisfied with static_assert on some type traits? Not sure if there is one about const members... – Amomum Oct 29 '19 at 12:56
  • 1
    @Amomum I don't think being subobject is something that can be tested. The const or reference members would make the class non-assignable, which I don't think can happen otherwise for a trivial class. – eerorika Oct 29 '19 at 13:04
  • does this mean strict-aliasing comes into play regarding the constness ? can you provide an example where this might come into play ? – darune Oct 29 '19 at 13:08
  • 1
    @darune Create a const object, placement-new onto it, use the original name of the object (or a pre-existing pointer or refernce) and the behaviour will be undefined. Pretty much same in effect as strict aliasing optimisation, if not the very same altogether. – eerorika Oct 29 '19 at 13:12
  • 1
    The inverse of `delete ptr` is `new T()`. The inverse of `new(ptr)T{}` is `ptr->~T();`. https://stackoverflow.com/a/8918942/845092 – Mooing Duck Oct 29 '19 at 21:32
  • @MooingDuck `delete ptr` is quite similar to `new(ptr)T{}` in that both end the lifetime of the trivial object that was pointed by `ptr`. – eerorika Oct 29 '19 at 22:49
  • @eerorika And quite different that `delete ptr` deallocates memory, wheras `new(ptr)T{}` does not allocate. `new T()` allocates and constructs. `ptr->~T()` destructs but does not deallocate. – Mooing Duck Oct 29 '19 at 23:08
  • @MooingDuck Indeed. Similarity in one aspect does not by itself guarantee similarity in all aspects. – eerorika Oct 29 '19 at 23:16
  • How important is it that the class should be trivial? (Adding `A() {};` would be enough to make the OP's example class non-trivial.) – user2023370 Jul 26 '23 at 17:05
7

The rules that cover this are in [basic.life]/5

A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type. For an object of a class type, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression is not used to release the storage, the destructor is not implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

and [basic.life]/8

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

  • the storage for the new object exactly overlays the storage location which the original object occupied, and

  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and

  • the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and

  • neither the original object nor the new object is a potentially-overlapping subobject ([intro.object]).

Since your object is trivial you don't have to worry about [basic.life]/5 and as long as you satisfy the bullet points from [basic.life]/8, then it is safe.

Community
  • 1
  • 1
NathanOliver
  • 171,901
  • 28
  • 288
  • 402