Lumiera
0.pre.03
»edit your freedom«
|
Go to the source code of this file.
A mechanism to allow for opaque polymorphic value objects.
This template helps to overcome a problem occasionally encountered in C++ programming, based on the fundamental design of C++, which favours explicit low-level control, copying of values and strict ctor-dtor pairs. Many object oriented design patterns build on polymorphism, where the actual type of an object isn't disclosed and collaborations rely on common interfaces. This doesn't mix well with the emphasis the C/C++ language puts on efficient handling of small data elements as values and explicit control of the used storage; indeed several of the modern object oriented and functional programming techniques more or less assume the presence of a garbage collector or similar mechanism, so 'objects' need just to be mentioned by reference.
In C++, in order to employ many of the well known techniques, we're bound more or less to explicitly put the objects somewhere in heap allocated memory and then pass an interface pointer or reference into the actual algorithm. Sometimes, this hinders a design based on constant values and small descriptor objects used inline, thus forcing into unnecessarily complex and heavyweight alternatives. While it's certainly pointless to fight the fundamental nature of the programming language, we may try to pull some (template based) trickery to make polymorphic objects fit better with the handling of small copyable value objects. Especially, C++ gives a special meaning to passing parameters as const&
– typically constructing an anonymous temporary object conveniently just for passing an abstraction barrier (while the optimiser can be expected to remove this barrier and the accompanying nominal copy operations altogether in the generated code). Consequently the ability to return a polymorphic object from a factory or configuration function by value would open a lot of straight forward design possibilities and concise formulations.
So the goal is to build a copyable and assignable type with value semantics, without disclosing the actual implementation and object layout at the usage site. This seemingly contradictory goal can be achieved, provided that
The PolymorphicValue template implements this idea, by exposing a copyable container with value semantics to the client code. On instantiation, a common base interface for the actual value objects needs to be provided; the resulting instance will be automatically convertible to this interface. Obviously this common interface must be an ABC or at least contain some virtual functions. Moreover, the PolymorphicValue container provides static builder functions, allowing to place a concrete instance of a subclass into the content buffer. After construction, the actual type of this instance will be forgotten ("type erasure"), but because of the embedded vtable, on access, the proper implementation functions will be invoked.
Expanding on that pattern, the copying and cloning operations of the whole container can be implemented by forwarding to appropriate virtual functions on the embedded payload (implementation) object – the concrete implementation of these virtual functions can be assumed to know the real type and thus be able to invoke the correct copy ctor or assignment operator. For this to work, the interface needs to expose those copy and clone operations somehow as virtual functions. There are two alternatives to fulfil this requirement:
dynamic_cast<CopyAPI&>(bufferContents)
static_cast
. Indeed, as we're just using a different meaning of the VTable, only a single indirection (virtual function call) is required at runtime in this case to invoke the copy ctor or assignment operator. Thus, in this latter (optimal) case, the fact that PolymorphicValue allows to conceal the actual implementation type comes with zero runtime overhead, compared to direct usage of a family of polymorphic types (with VTable).So, how can the implementation of copy or assignment know the actual type to be copied? Basically we exploit the fact that the actual instance lives in an opaque buffer within the "outer" container. More specifically, we place it into that buffer – thus we're able to control the actual type used. This way, the actual copy operations reside in an Adapter type, which lives at the absolute leaf end of the inheritance chain. It even inherits from the "implementation type" specified by the client. Thus, within the context of the copy operation, we know all the concrete types.
To start with, we need a situation where polymorphic treatment and type erasure might be applicable. That is, we use a public API, and only that, in any client code, while the concrete implementation is completely self contained. Thus, in the intended use, the concrete implementation objects can be assembled once, typically in a factory, and after that, no further knowledge of the actual implementation type is required. All further use can be coded against the exposed public API.
Given such a situation, it might be desirable to conceal the whereabouts of the implementation completely from the clients employing the generated objects. For example, the actual implementation might rely on a complicated subsystem with many compilation dependencies, and we don't want to expose all those details on the public API.
Now, to employ PolymorphicValue in such a situation, on the usage side (header):
On the implementation side (separate compilation unit)
Definition in file polymorphic-value.hpp.
#include "lib/error.hpp"
#include "lib/meta/duck-detector.hpp"
#include "lib/util.hpp"
#include <cstddef>
#include <new>
Classes | |
class | PolymorphicValue< IFA, storage, CPY >::Adapter< IMP > |
Implementation Helper: add support for copy operations. More... | |
class | allow_Clone_but_no_Copy< T > |
helper to detect if the API supports only copy construction, but no assignment More... | |
struct | AssignmentPolicy< API, YES > |
Policy class for invoking the assignment operator. More... | |
struct | AssignmentPolicy< API, enable_if< allow_Clone_but_no_Copy< API > > > |
special case when the embedded payload objects permit copy construction, but no assignment to existing instances. More... | |
class | CloneValueSupport< BA > |
A variation for limited copy support. More... | |
class | CopySupport< IFA, BA > |
Interface for active support of copy operations by the embedded client objects. More... | |
struct | EmptyBase |
class | exposes_CloneFunction< T > |
helper to detect presence of a function to support clone operations More... | |
class | PolymorphicValue< IFA, storage, CPY > |
Template to build polymorphic value objects. More... | |
struct | Trait< TY, YES > |
trait template to deal with different ways to support copy operations. More... | |
struct | Trait< TY, enable_if< exposes_CloneFunction< TY > > > |
Special case when the embedded types support copying on the API level, e.g. More... | |
Namespaces | |
lib | |
Implementation namespace for support and library code. | |