C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Replace an object -- but retain old object if new object fails to construct

From: Sebastian Wittmeier <wittmeier_at_[hidden]>
Date: Tue, 21 Oct 2025 18:04:59 +0200
Interesting problem statement. This has effects on lifetime and identity.   During the call of the constructor the (possibly) to be replaced object would obviously be unavailable. The pointers are invalid and the member variables are not in the possibly marked memory. There is a reason for the object not being movable.   This can lead to three issues:  - The owner of the object knows, what they did to the object. But the object itself does not. And during that time code is executed (the constructor of the new object). That code can indirectly access the object (e.g. over some object registry, e.g. all old objects are notified about a new construction)    - For a short time the newly constructed object and the old object are at the same position in memory. That can lead to identity problems. (The question is, whether the new object can rely on its identity, before the constructor has finished). If we keep the example of a registry, it would get a second entry for the same location.    - When the object is finally destructed (after a successful constructor) the immovable object is at a different location in memory during the destructor call   -----Ursprüngliche Nachricht----- Von:Frederick Virchanza Gotham via Std-Proposals <std-proposals_at_[hidden]> Gesendet:Di 21.10.2025 18:03 Betreff:[std-proposals] Replace an object -- but retain old object if new object fails to construct An:std-proposals <std-proposals_at_[hidden]>; CC:Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>; There is more than one scenario in which I would want to replace an old object with a new object, but for this use case I'll focus on std::optional. Let's say we have a variable of type std::optional<std::mutex>. Yes you guessed right, I picked my favourite class 'std::mutex' because it's both unmovable and uncopyable. So let's start off by creating our variable:    std::optional<std::mutex> om;    om.emplace(); Now . . . . later on in the program, we want to replace the contained object -- but only if the construction of the new object is successful. If the construction of the new object fails, we want to retain the old object. So here's the strategy: (Step 1) Copy the bytes of the current object into a temporary buffer on the stack (Step 2) Construct the new object at the original location of the old object (i.e. overwrite the old object) (Step 3) If constructor throws an exception, move the old object's bytes back from the temporary buffer (i.e. restore the old object) (Step 4) Otherwise, if construction succeeds, swap the two buffers, call the destructor to destroy the old object, then swap the two buffers again So the implementation of a free-standing function would look like this:    template<StdOptional Opt, typename... Params>    constexpr bool emplace_or_retain(Opt &&opt, Params&&... args) noexcept    {        typedef typename std::remove_cvref_t<Opt>::value_type T;        if ( false == opt.has_value() )        {            try            {                opt.emplace( std::forward<Params>(args)... );                return true;            }            catch(...){}            return false;        }        // (1) Copy old object's bytes to temporary buffer        T *const p = std::addressof( opt.value() );        std::byte backup[sizeof(T)];        std::memcpy(backup, p, sizeof backup);        // (2) Try to construct the new object in-place (overwriting the old one)        try        {            std::construct_at( p, std::forward<Params>(args)... );            // (3) Construction succeeded, so swap the two buffers,            //     destroy the old object, and then move the new object back            std::swap_ranges( backup,                            backup + sizeof backup,                            static_cast<std::byte*>(static_cast<void*>(p)) );            try { std::destroy_at(p); } catch(...){}            std::memcpy( p, backup, sizeof backup );            return true;        }        catch (...){}        // (4) Construction failed: restore old object        std::memcpy( p, backup, sizeof backup );        return false;    } And here it is tested up on GodBolt:    https://godbolt.org/z/cxf3Wqdhe Now see here's the thing . . . I'm probably more of a firmware engineer than a software engineer, and so I have a little difficulty maintaining respect for the "object model" since I deal so much with copper and voltages and CPU registers, but I can at least concede that my above code absolutely decimates the object model in the most horrendous and unspeakable manner. Also I'm pretty sure I'm not allowed to 'memcpy' a class like 'std::mutex' either. So anyway, to get around the whole object model boggle and memcpy's class bytes, perhaps could the standard library be given a new function called something like "emplace_or_retain", and so then compiler vendors could take my above code and put it in their header files and then put some fine print with it saying "The compiler maintains the sanctity of the C++ object model when compiling this code". -- Std-Proposals mailing list Std-Proposals_at_[hidden] https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2025-10-21 16:17:57