Date: Tue, 21 Oct 2025 17:03:28 +0100
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".
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".
Received on 2025-10-21 16:03:41
