juli (caladri) wrote in dailysrc,
juli
caladri
dailysrc

doing interface-based programming in C++.

In C++, I've wanted do do interface-based programming, and further, be able to wrap things at the interface level so that I could make locking part of the interface, or possibly do an asynchronous or remote function call without the caller having to know or do anything special. A few weeks ago I came up with a way to do this by stacking vtables. Note that this version is simple and uses two virtual methods. A more complex version uses a templated IBase to avoid one virtual method, which should be fairly obvious. Note that this is cumbersome, but I've written (and am using) a proof-of-concept interface generator to do this stuff for me, and I've already begun using it to make invariants and locking and whatnot a part of the exported interface/contract in my code.

Note that this is also a good interview question, if you're so inclined. I came up with this with a lot of help from arjache.

#include <iostream>

class Base {
protected:
        Base(void)
        {
                std::cout << __PRETTY_FUNCTION__ << std::endl;
        }

        virtual ~Base()
        {
                std::cout << __PRETTY_FUNCTION__ << std::endl;
        }
public:
        void method(const char *string)
        {
                std::cout << __PRETTY_FUNCTION__ << std::endl;
                methodWrapper(string);
                std::cout << __PRETTY_FUNCTION__ << std::endl;
        }
private:
        virtual void methodWrapper(const char *) = 0;
};

class IBase : public Base {
protected:
        IBase(void)
        {
                std::cout << __PRETTY_FUNCTION__ << std::endl;
        }

        virtual ~IBase()
        {
                std::cout << __PRETTY_FUNCTION__ << std::endl;
        }
private:
        void methodWrapper(const char *string)
        {
                std::cout << __PRETTY_FUNCTION__ << std::endl;
                method(string);
                std::cout << __PRETTY_FUNCTION__ << std::endl;
        }
        virtual void method(const char *) = 0;
};

class MyClass : public IBase {
public:
        MyClass(void)
        {
                std::cout << __PRETTY_FUNCTION__ << std::endl;
        }

        ~MyClass()
        {
                std::cout << __PRETTY_FUNCTION__ << std::endl;
        }
private:
        void method(const char *string)
        {
                std::cout << string << std::endl;
        }
};

void
hello(Base *base)
{
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        base->method("Hello, world!");
        std::cout << __PRETTY_FUNCTION__ << std::endl;
}

int
main(void)
{
        MyClass test;
        hello(&test);
        return (0);
}

  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

  • 4 comments
Base::method could only be asynchronous for functions returning no values, right?

Another way to do this is:

class IBase { public: void method() { methodWrapper(); } protected: abstract void methodWrapper() = 0; }
class MyClass { void methodWrapper() {} }

Or am I over simplifying things? Sorry if so, I've been stuck in Java lately.
It could also be methods returning an asynchronous result which you could call ->end() or something on to block on. So, one of the things I wanted to do was to not have a different name, signature, etc., for the method in the interface or in the implementation. This stemmed from an initial goal of not needing any special syntax at all (to build a message passing system behind this.) Basically I wanted to hide all the implementation details from bother the caller and the callee. The only change (which is explainable as simply a different style) is that for an interface named Base, classes implementing it would inherit from IBase and anything referring to something using that interface would refer to it as Base.
Neat. I like how the thin IBase layer prevents syntax scabbing and lets IBase decide how MyClass is used.

I have one quick comment on the naming scheme. In MSCOM, the "I" prefix went on the caller-facing class: hello(IBase *base). The "IBase" here would be something like "IBaseShim" or something similar. Just a thought, since the reversal threw me :)

You might look at Mozilla's XPCOM. Maybe it would be a good reference for marshalling arguments. I don't know if it handles RPC, but I'm pretty sure it handles the good ol' interface querying and adding/releasing of references.

Some chief architects at my last job had some interesting discussions on efficiently accessing elements in an array passed between processes. They went through a lot of trouble, albeit with a cross platform implementation.
The naming scheme choice was intentional, though I've done .net work and I know that the IFoo is an undesirable collision there... However, it appeals to me to call what external things see "X" and let a class itself know that it extends/implements "X"... I suppose it comes from doing a lot of ObjC and liking that consumers see the @interface/@end and the implementation references that (or an @protocol) while providing its own (non-visible) @implementation.

As for marshalling arguments, in a C++y way, what I like about doing things this way is one can *very* trivially auto-generate something like:

class IBase : public Base {
    class methodDispatch : public IDispatch {
        methodDispatch(IBase *b, const char *string) : b_(b), string_(string) {
            deferred_dispatch(this);
        }
        void dispatch(void) {
            b_->method(string_);
        }
    }
    void methodWrapper(const char *string) {
        if (async) { new methodDispatch(string); }
        else { method(string); }
    }
};


Roughly... You get the idea... Doing it that way static types (1) which method is called (2) the context in which it is called (3) the arguments. The mechanics of marshalling the arguments is easy enough by just providing standard << and >> overloads for i/ostream and whatever datum. As for passing arrays, one of the things I like about this, is you static type at the sender and receiver [and in between, but that's transparent], and since it's C++, one could easily see requiring the use of some random type of container class which would happen to be serializable.

To draw a similarity to a big Objective-C application I'm working on... Every class implemented in it must be "IVerifiable" and it is very frequent to have a [self validate] call which calls [foo verify] which calls [super verify] until you reach IVerifiable. If nothing returns "false" then the object is valid. I've got a lot of things like that that I'm making myself do so that all of my operations can be checked and so on. I like requiring those sorts of standards for sufficiently complicated C++ apps as well, if only because C++ provides so many more blunt instruments (and in such pretty, tempting colors) to beat your foot off with.