I have a class Obj
with a const member i
:
class Obj {
const int i;
...
};
But I need to set i to 0 in my move constructor. (Because if i
isn't 0
, the destructor will delete stuff, and since I moved the object, that will result in a double free)
Is it safe to modify Obj::i
in the move constructor like this?
Obj::Obj(Obj &&other) :
i(other.i)
{
std::destroy_at(&other.i);
std::construct_at(&other.i, 0);
}
From how I understand it, it is safe to do this when std::construct_at
replaces other.i with a "transparently replaceable object". But I'm not completely sure what the definition means:
(8) An object o1 is transparently replaceable by an object o2 if:
(8.1) the storage that o2 occupies exactly overlays the storage that o1 occupied, and
(8.2) o1 and o2 are of the same type (ignoring the top-level cv-qualifiers), and
(8.3) o1 is not a complete const object, and
(8.4) neither o1 nor o2 is a potentially-overlapping subobject ([intro.object]), and
(8.5) either o1 and o2 are both complete objects, or o1 and o2 are direct subobjects of objects p1 and p2, respectively, and p1 is transparently replaceable by p2.
(https://eel.is/c draft/basic#life-8)
From my understanding, at least 8.1, 8.2, and 8.3 apply, but I'm not completely sure, and I don't really understand 8.4 and 8.5.
So am I correct in thinking this should work (in C 20), or would this result in undefined behavior?
CodePudding user response:
A potentially-overlapping subobject is a base class subobject or a member marked with [[no_unique_address]]
. Obj::i
is not so 8.4 applies.
If you take p1 and p2 to be the same object, other
, then 8.5 probably applies (an object can transparently replace itself), except in that it doesn't apply recursively (e.g., Obj
is a base class/[[no_unique_address]]
member of some other class, or the complete object it is part of is const
and other
has been const_cast
or is a mutable member). But it will practically always apply.
But consider just not making it a const
member, since you do need to modify it here. Your move constructor should also clear out other
(e.g., setting any pointers to nullptr
, clearing any file handles, zeroing other stuff), so there is no chance for the destructor to accidentally double delete stuff.