There is a misused contruction, here:
class A {
A& operator=(const A& rhs) {
if(&a==this) return *this;
this->~A();
new(this) A(rhs);
return *this;
}
A& operator=(A&& rhs) {
if(&a==this) return *this;
this->~A();
new(this) A(std::move(rhs));
return *this;
}
// etc.
};
This is correct respect to the inplace ctor/dtor semantics, and thsi is what std::allocator
does to destroy and construct elements in a buffer, so that must be correct, right?
Well... not properly: it all is about what A in fact contains and what the A ctor actually does.
If A just contains basic types and does not own resources that's fine, it works. It's just not idiomatic, but correct.
If A contains some other resources, that need to be acquired, managed and released well... you may be in trouble. And you also are if A is polymorphic (if ~A is virtual you destroy the entire object, but then you reconstruct just the A subobject).
The problem is that a constructor that acquires resources may fail, and an object that fails in construction and throws must not be destroyed since it has been never "constructed".
But if you are "assigning", you are not "creating", and if the in-place ctor fails, your object will exist (because it pre-exist in its own scope), but is in a state that cannot be managed by a further destruction: think to
{
A a,b;
a = b;
}
At the }
b and a will be destroyed but if A(const A&) failed in a=b, and a throw
is made in A::A
, a
is not existing, but will be improperly destroyed at the }
that throw
will immediately jump to.
A more idiomatic way is to have
class A
{
void swap(A& s) noexcept
{ /* exchanging resources between existing objects should never fail: you just swap pointers */ }
public:
A() noexcept { /* creates an object in a "null" recognizable state */ }
A(const A& s) { /* creates a copy: may fail! */ }
A(A&& s) noexcept { /*make it as null and... */ swap(s); } // if `s` is temporary will caryy old resource deletionon, and we keep it's own resource going
A& operator=(A s) noexcept { swap(s); return *this; }
~A() { /* handle resource deletion, if any */ }
};
Now,
a=b
will create a b
copy as the s
parameter in operator=
(by means of A::A(const A&)
).
If this fails, s
will not exist and a
and b
are still valid (with their own old values), hence at scope exiting will be destroyed as normally.
If the copy succeed, the copyed resources and the actual a
's will be exchanged, and when s
dies at the }
the old-a resources will be freed.
By converse
a = std::move(b)
Will make b
as-temporary, the s
parameter constructed via A(A&&), so b will swap with s (and becomes null) than s will swap with a. At the end, s
will destroy old a
resources, a
will receive old b
's and b will be in null state (so it can die peacefully when its scope ends)
The problem of "making A as null" must be implemented in both A()
and A(A&&)
.
This may be by means of an helper member (an init
, just like a swap
) or by specifying member initializers, or by defining default initialization values for members (once for all)