In an old project I tinker with from time to time, I have a DOM-like structure in an MMF database. I'd like the nodes to act like they have some C++ typing based on the content, and accessor methods that go along with that. Yet in actuality, the data is independent of the type system in C++.
The problems with trying to do this are pretty analogous to situations like Object Relational Impedance Mismatch. So it's not going to be perfect; it's more of a helpful bookkeeping tool than anything. Some background reading of prior feedback: first, second
Another thing I wanted was to get solid control of the nodes. I don't want client code to leak them, or store handles to them past certain clearly delineated phases. This means I need something akin to smart pointers...and all factory creation with private destructors that are friends of std::default_delete
. I also have to bundle some context information with the handle instances that are passed into user code.
I've simplified the pattern down a single-file example that with a really basic datatype that you can compile, if you like:
But I've included the relevant classes in a reduced form here. There's an internal class called FooPrivate, which is very simple; representing our basic behavior of the data item:
class FooPrivate final { /* ... */
private:
FooPrivate (int value) : _value (value) { }
~FooPrivate () { }
private:
static unique_ptr<FooPrivate> create(int value) {
return unique_ptr<FooPrivate> (new FooPrivate (value));
}
public:
FooPrivate * consume(unique_ptr<FooPrivate> && other) {
_value += other->_value;
FooPrivate * result (other.release());
consumed.push_back(result);
return result;
}
void setValue(int value) { _value = value; }
int getValue() const { return _value; }
private:
int _value;
vector<FooPrivate *> consumed;
};
Simple case here for the data is you can access an integer with getValue()
and setValue()
. A consume()
function is an example of something that takes transfer of ownership to the data structure.
The next step is a handle and accessor for client use called Foo. For simplicity, the context is just a boolean. Each Foo contains a raw FooPrivate pointer and a shared pointer to a Context.
class Context final { /* ... */
private:
Context (bool valid) : _valid (valid) { }
private:
bool _valid;
};
Foo has a method matching every method in FooPrivate. But there is added checking of the Context, as well as preventing raw FooPrivate pointers from leaking into user code:
class Foo { /* ... */
private:
void setInternals(FooPrivate * fooPrivate, shared_ptr<Context> context) {
_fooPrivate = fooPrivate;
_context = context;
}
protected:
Foo () {
setInternals(nullptr, nullptr);
}
private:
Foo (FooPrivate * fooPrivate, shared_ptr<Context> context) {
setInternals(fooPrivate, context);
}
FooPrivate const & getFooPrivate() const {
if (not _context->_valid) {
throw "Attempt to dereference invalid Foo handle.";
}
return *_fooPrivate;
}
FooPrivate & getFooPrivate() {
if (not _context->_valid) {
throw "Attempt to dereference invalid Foo handle.";
}
return *_fooPrivate;
}
public:
template<class FooType>
Reference<FooType> consume(Owned<FooType> && foo) {
return Reference<FooType> (
getFooPrivate().consume(move(foo.extractFooPrivate())),
shared_ptr<Context> (new Context (false))
);
}
void setValue(int value) { getFooPrivate().setValue(value); }
int getValue() const { return getFooPrivate().getValue(); }
private:
FooPrivate * _fooPrivate;
shared_ptr<Context> _context;
};
Further up from that is a template for Reference. It's basically equivalent to a Foo, but is parameterized by a subclass of Foo. It uses the pointer dereference operator to invoke methods coming from that subclass.
template<class FooType>
class Reference { /* ... */
private:
Reference (FooPrivate * fooPrivate, shared_ptr<Context> context) {
/* ... */
_foo.setInternals(fooPrivate, context);
}
public:
Reference (Foo & foo) : _foo (foo) { }
template<class OtherFooType>
Reference (Reference<OtherFooType> & other) {
/* ... */
_foo.setInternals(other._fooPrivate, other._context);
}
FooType * operator-> () { return &_foo; }
FooType const * operator-> () const { return &_foo; }
FooType & getFoo() { return _foo; }
FooType const & getFoo() const { return _foo; }
private:
FooType _foo;
};
Finally there is Owned; an analogue to unique_ptr that has similar behavior to FooReference. The Foo version of consume--for instance--takes an Owned<FooType>
instead of a unique_ptr<FooPrivate>
.
template<class FooType>
class Owned { /* ... */
private:
Owned (
unique_ptr<FooPrivate> && fooPrivate, shared_ptr<Context> context
) {
/* ... */
_foo.setInternals(fooPrivate.release(), context);
}
unique_ptr<FooPrivate> extractFooPrivate() {
unique_ptr<FooPrivate> result (_foo._fooPrivate);
_foo.setInternals(nullptr, nullptr);
return result;
}
/* ... */
public:
static Owned<FooType> create(int value) {
return Owned<FooType> (
FooPrivate::create(value),
shared_ptr<Context> (new Context (true))
);
}
template<class OtherFooType>
Owned (Owned<OtherFooType> && other) {
/* ... */
_foo.setInternals(
other.extractFooPrivate().release(),
other._foo._context
);
}
template<class OtherFooType>
Owned<FooType> operator= (Owned<OtherFooType> && other) {
/* ... */
return Owned<FooType> (other.extractFooPrivate(), other._context);
}
FooType * operator-> () { return &_foo; }
FooType const * operator-> () const { return &_foo; }
FooType & getFoo() { return _foo; }
FooType const & getFoo() const { return _foo; }
private:
FooType _foo;
};
To make things extra boring for this reduced example, you can't use that Reference for anything after the transfer (to demonstrate an instance of expiring a Context without adding more methods or other classes).
Here is a useless demonstration:
class DoublingFoo : public Foo {
public:
int getDoubleValue() const {
return Foo::getValue() * 2;
}
Reference<Foo> consumeHelper(int value) {
return consume(Owned<Foo>::create(value * 2));
}
};
int main()
{
auto parent (Owned<DoublingFoo>::create(10));
auto child (Owned<Foo>::create(20));
parent->consumeHelper(30);
Reference<Foo> childReference (parent->consume(move(child)));
cout << "The value is " << parent->getDoubleValue() << "\n";
return 0;
}
I guess some of my big questions would be:
Have any existing library authors faced a similar desire and done it better?
Any undefined behavior or other Really Bad Things I'm setting myself up for?
The technique seems to require those making Foo-derived classes to inherit publicly from Foo. But narrowing the methods vs. just adding to them could be useful too. How could one do something like this with protected inheritance?
Is putting the
create()
method as a static member of Owned the right place for it? Is there a good way to make it so that the authors of a Foo subclass could more elegantly make their own construction methods, while keeping the tight control of making sure everything is wrapped in an Owned?
But all feedback is welcome. So if anyone wants to get out a red pen and suggest better style on something not directly related to the pattern, that's great too...