On 11/01/2023 22:35, Hans Dembinski wrote:
Yes, FooOld2 is also unsafe, but you can use a rollback guard to undo the move.
FooOld3(bool b, T&& targ = {}) : t_(std::move(targ)) { rollback r(targ, t_); // moves t_ back to targ if not deactivated if (b) throw 1; r.deactivate(); }
Note that a rollback implementation is still unsafe if the type in question has a noexcept(false) move constructor -- which, while unusual, is legal -- and might be more common than you think when it decays to use the copy constructor instead. (This can also be bad for performance, though only when throwing, so you likely don't care.) A common surprise is that copy-constructor-only types will still satisfy std::is_move_constructible_v, although typically not std::is_nothrow_move_constructible_v. (Which is one of the reasons why the "rule of five" exists now.) (You might ask why this is unsafe, since we only care about exceptions thrown in the constructor body -- if the initial parameter move/copy throws that's a different case, right? But consider that the initial parameter move/copy might succeed, the constructor body throws, and then the rollback move/copy also throws. This is especially likely if the constructor throw was a std::bad_alloc, but other cases are possible.) This isn't to say that the above is bad; but if you use it, don't forget to static_assert your assumptions (or provide alternative implementations).