Lib/Diff: extend PlantingHandle to allow for placment-new

...this extension was spurred by the previeous refactoring.
Since 'emplace' now clearly denotes an operation to move-embed an existing object,
we could as well offer a separate 'create' API, which would take forwarding
arguments as usual and just delegates to the placement-new operation 'create'
already available in the InPlaceBuffer class.

Such would be a convenience shortcut and is not strictly necessary,
since move-construction is typically optimised away; yet it would also
allow to support strictly non-copyable payload types.

This refactoring also highlights a fuzziness in the existing design,
where we just passed the interface type, while being sloppy about the
DEFAULT type. In fact this *is* relevant, since any kind of construction
might fail, necessitating to default-construct a placeholder, since
InPlaceBuffer was intended for zero-overhead usage and thus has in itself
no means to know about the state of its buffer's contents. Thus the
only sane contract is that there is always a valid object emplaced
into the buffer, which in turn forces us to provide a loophole for
class hierarchies with an abstract base class -- in such a case the
user has to provide a fallback type explicitly.
This commit is contained in:
Fischlurch 2021-05-02 19:40:11 +02:00
parent 5aa41accfc
commit 5a37bce855
4 changed files with 94 additions and 26 deletions

View file

@ -109,7 +109,7 @@
namespace lib {
template<class BA>
template<class BA, class DEFAULT>
class PlantingHandle;
namespace idi {
@ -511,7 +511,7 @@ namespace diff{
/* === low-level access (for diff application) === */
using BufferHandle = PlantingHandle<TreeMutator>;
using BufferHandle = PlantingHandle<TreeMutator, TreeMutator>;
/** attachment point to receive and apply tree-diff changes.
* The actual implementation needs to be provided for concrete Record payload types;

View file

@ -571,6 +571,9 @@ namespace lib {
template<class BA, class DEFAULT>
class PlantingHandle;
/**
* Buffer to place and maintain an object instance privately within another object.
* Variation of a similar concept as with OpaqueHolder, but implemented here
@ -652,7 +655,10 @@ namespace lib {
* Use as `InPlaceBuffer(embedType<XYZ>, arg1, arg2, arg3)` */
template<typename SUB>
static auto embedType() { return (SUB*) nullptr; }
/** a "planting handle" can be used to expose an opaque InPlaceBuffer through an API */
using Handle = PlantingHandle<BA, DEFAULT>;
/** Abbreviation for placement new */
template<class TY, typename...ARGS>
@ -713,7 +719,7 @@ namespace lib {
* @warning the type BA must expose a virtual dtor, since the targeted
* InPlaceBuffer has to take ownership of the implanted object.
*/
template<class BA>
template<class BA, class DEFAULT = BA>
class PlantingHandle
{
void* buffer_;
@ -723,31 +729,18 @@ namespace lib {
"target interface BA must provide virtual dtor, "
"since InPlaceBuffer needs to take ownership.");
template<class SUB>
void __ensure_can_create();
public:
template<size_t maxSiz>
PlantingHandle (InPlaceBuffer<BA, maxSiz>& targetBuffer)
PlantingHandle (InPlaceBuffer<BA, maxSiz, DEFAULT>& targetBuffer)
: buffer_(&targetBuffer)
, maxSiz_(maxSiz)
{ }
template<class SUB>
BA&
emplace (SUB&& implementation)
{
if (sizeof(SUB) > maxSiz_)
throw error::Fatal("Unable to implant implementation object of size "
"exceeding the pre-established storage buffer capacity. "
+boost::lexical_cast<std::string>(sizeof(SUB)) + " > "
+boost::lexical_cast<std::string>(maxSiz_)
,error::LUMIERA_ERROR_CAPACITY);
using Holder = InPlaceBuffer<BA, sizeof(SUB)>;
Holder& holder = *static_cast<Holder*> (buffer_);
return holder.template create<SUB> (std::forward<SUB> (implementation));
}
template<class SUB>
bool
canCreate() const
@ -755,6 +748,33 @@ namespace lib {
return sizeof(SUB) <= maxSiz_;
}
/** move-construct an instance of subclass into the opaque buffer */
template<class SUB>
SUB&
emplace (SUB&& implementation)
{
__ensure_can_create<SUB>();
using Holder = InPlaceBuffer<BA, sizeof(SUB), DEFAULT>;
Holder& holder = *static_cast<Holder*> (buffer_);
return holder.template create<SUB> (std::forward<SUB> (implementation));
}
/** Abbreviation for placement new of a subclass SUB into the opaque buffer*/
template<class SUB, typename...ARGS>
SUB&
create (ARGS&& ...args)
{
__ensure_can_create<SUB>();
using Holder = InPlaceBuffer<BA, sizeof(SUB), DEFAULT>;
Holder& holder = *static_cast<Holder*> (buffer_);
return holder.template create<SUB> (std::forward<ARGS> (args)...);
}
BA*
get() const
{
@ -766,6 +786,23 @@ namespace lib {
/** @internal Helper to ensure the opaque buffer provides sufficient storage
* @tparam SUB actual subclass type to be implanted into the opaque buffer
*/
template<class BA, class B0>
template<class SUB>
inline void
PlantingHandle<BA,B0>::__ensure_can_create()
{
if (not this->canCreate<SUB>())
throw error::Fatal("Unable to implant implementation object of size "
"exceeding the pre-established storage buffer capacity. "
+boost::lexical_cast<std::string>(sizeof(SUB)) + " > "
+boost::lexical_cast<std::string>(maxSiz_)
,error::LUMIERA_ERROR_CAPACITY);
}
} // namespace lib
#endif

View file

@ -121,7 +121,7 @@ namespace test{
* due to alignment of the contained object
* within OpaqueHolder's buffer
*/
const size_t _ALIGN_ = sizeof(size_t);
const size_t _ALIGNMENT_OVERHEAD_ = sizeof(size_t);
}
@ -141,10 +141,10 @@ namespace test{
_checksum = 0;
_create_count = 0;
{
typedef InPlaceBuffer<Base, sizeof(DD<42>), DD<0>> Buffer;
using Buffer = InPlaceBuffer<Base, sizeof(DD<42>), DD<0>>;
Buffer buff;
CHECK (sizeof(buff) <= sizeof(DD<42>) + _ALIGN_);
CHECK (sizeof(buff) <= sizeof(DD<42>) + _ALIGNMENT_OVERHEAD_);
CHECK (1 == _create_count);
CHECK (0 == _checksum);
buff->confess(); // one default object of type DD<0> has been created
@ -159,7 +159,14 @@ namespace test{
CHECK(0 == buff->id_); // default object was created, due to exception...
buff.create<D42Sub> ("what the f**","is going on here?");
// as a variation: use a "planting handle" to implant yet another subtype
// into the opaque buffer. This setup helps to expose such a buffer via API
using Handle = Buffer::Handle;
Handle plantingHandle{buff};
plantingHandle.create<D42Sub> ("what the f**","is going on here?");
// subclass instance was indeed implanted into the opaque buffer
buff->confess();
CHECK (6 == _create_count);

View file

@ -33477,6 +33477,30 @@
<node COLOR="#338800" CREATED="1619972797494" ID="ID_1468885512" MODIFIED="1619972821488" TEXT="nebenbei: Operation &quot;create&quot; &#x2192; &quot;emplace&quot;">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1619976082135" ID="ID_416166359" MODIFIED="1619976677236" TEXT="Variante f&#xfc;r placement-new einbauen">
<icon BUILTIN="button_ok"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1619976099788" ID="ID_1451882006" MODIFIED="1619976684789" TEXT="Problem: DEFAULT-Typ und SIZE-Parameter in das Handle einbetten">
<icon BUILTIN="messagebox_warning"/>
</node>
<node COLOR="#435e98" CREATED="1619976624404" ID="ID_1394264630" MODIFIED="1619976674356" TEXT="DEFAULT-Typ kann als Typ-Parameter weiteregeben werden">
<icon BUILTIN="idea"/>
<node CREATED="1619976641856" ID="ID_960415515" MODIFIED="1619976671474" TEXT="dann wird eben auch der Typ vom Handle komplexer">
<icon BUILTIN="yes"/>
</node>
<node COLOR="#338800" CREATED="1619976655262" ID="ID_411012847" MODIFIED="1619976666398" TEXT="daf&#xfc;r in InPlaceBuffer einen Typ-Konstruktor anbieten">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1619976688954" ID="ID_1451984496" MODIFIED="1619976700633" TEXT="nochmal &#xfc;ber den Size-Parameter nachdenken...">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1619976702067" ID="ID_1433875229" MODIFIED="1619976734718" TEXT="kann man den dann auch zur Compile-Zeit weitergeben?">
<icon BUILTIN="help"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1619976718142" ID="ID_210847961" MODIFIED="1619976734718" TEXT="wie sieht&apos;s mit Assignments aus?">
<icon BUILTIN="help"/>
</node>
</node>
</node>
</node>
</node>
</node>