10
\$\begingroup\$

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:

facade.cpp (gist.github.com)

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...

\$\endgroup\$
2
  • \$\begingroup\$ Would you mind including the other classes from Github into the text of this question? I'm having a hard time understanding it based on only your descriptions of these classes, without seeing the code on Github. For details please read Can I put my code on a third party site and link to the site in my question? \$\endgroup\$
    – ChrisW
    Jan 27, 2014 at 14:44
  • \$\begingroup\$ @ChrisW Wasn't aware of the FAQ issue. I'm still linking the code for those who might want to tinker with it in a fully compilable way, but have put in all the participating classes, minus static asserts / friend declarations / comments. Thanks! \$\endgroup\$ Jan 27, 2014 at 20:13

1 Answer 1

2
\$\begingroup\$

At least right now, I have only a few comments, and they're about style, not the pattern you've devised. First, I'm rather bothered by the number and redundancy of access specifiers in your code. For example:

class FooPrivate final { 

    friend class Foo;
    template<class> friend class Reference;
    template<class> friend class Owned;
    template<class> friend struct std::default_delete;

private:
    FooPrivate (int value) : _value (value) { }

    virtual ~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;
};

I'd rather see code that sorted the members so I can see all the public "stuff" together, and as soon as I get to a private: access specifier, know that the rest of the class is private.

struct FooPrivate final {
    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:
    friend class Foo;
    template<class> friend class Reference;
    template<class> friend class Owned;
    template<class> friend struct std::default_delete;

    FooPrivate(int value) : _value(value) { }

    virtual ~FooPrivate() { }

    static unique_ptr<FooPrivate> create(int value) {
        return unique_ptr<FooPrivate>(new FooPrivate(value));
    }

    int _value;
    vector<FooPrivate *> _consumed;
};

Second (shown in the code above), setValue and getValue bug the heck out of me. As it stands right, they provide no real encapsulation. You have, in effect, a public int, but with really ugly syntax. If the intent is really that _value be publicly accessible, just make it publicly accessible. Lots of people get religious about making data private, then having get/set pairs to essentially make it public again. It's a pointless waste of code not only in the class but also (worse) in the client code.

Yes, it's generally better to provide higher-level, more meaningful actions that simply retrieve the value and set the value--but in some cases that's really all you want or need--and when/if that's the case, don't try to hide it behind the subterfuge of a get/set pair. Do what it's going to do directly and cleanly by making that member public.

Conversely, if you're going to encapsulate that value, truly encapsulate it. Don't just hide it behind an accessor and mutator, but provide some meaningful higher-level constructs that keep the value truly hidden, and provide a higher level of abstraction to the outside world.

As it stands right now, the user gets the worst of both worlds--a low-level interface and ugly syntax, so (for example) code that could have been x._value += 5; ends up as: s.setValue(x.getValue() + 5);. This is not an improvement in any way.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for the feedback. This question has been around for a long time, and bountied twice, but as nobody seems to think it causes undefined behavior or anything, I'm going to assume it's okay. :-) I've been on the fence about whether to put all private members/methods at the end of class definitions. It does make it easier for others to use the header as documentation of a public interface, but it's not always an ideal grouping of related things while developing. I can't allow direct member access in the actual case, but I did lose the "get" so the pattern is more s.setValue(x.value()+5); \$\endgroup\$ Jul 8, 2014 at 14:53
  • \$\begingroup\$ Incidentally, if you are feeling generous and just want to weigh in on "hmm, I've seen something like that before" or "hey wait...that is a terrible idea" or "you know what might be better?" I did eventually put a completely gutted version of this project on GitHub, and I wrote a little blurb about it today: methyl.hostilefork.com \$\endgroup\$ Jul 8, 2014 at 18:03

Not the answer you're looking for? Browse other questions tagged or ask your own question.