This post is very old. Please bear in mind that information here might be incorrect or obsolete, and links can be broken. If something seems wrong, please feel free to comment or contact me and I'll update the post.
Ever had the problem that you have a base class, and a few of the derived classes share a certain interface and you didn’t want to pull it up to the main class because it drags a whole lot of dependencies with it? Then read on for how to write your own COM-like interface query.
The main idea is to have a single function in your base class (in this example, IBaseClass) which allows you to get various interfaces to it. You have to consider how to identify an interface, in this example, I use GUIDs which are reasonably simple to implement yet well suited by being … um, unique
.
So here we go: We create a base interface class (IInterface, or IUnknown) from which we derive our interfaces. Actually, you could skip that, too, but I think it can be useful for compile-time checks later. Next, we have a function getInterface (id, target) which returns us a pointer to where the interface is. In this example, we won’t do reference counting and thread-safe interfaces but a quick hack: The object creates an interface instance on load and always gives away a pointer to it. This works nicely if the interface is not too heavy and is reasonable unless you want to have more than one interface ready – but then, you should rather think about your class hierarchy
.
Here’s the example, pretty self-explaining:
#include <iostream>
#include <string>
// Simple POD Guid
struct GUID
{
bool operator==(const GUID& rhs) const
{
return a == rhs.a && b == rhs.b && c == rhs.c && d == rhs.d;
}
const unsigned int a, b, c, d;
};
// The base class for all interfaces
struct IInterface
{
static GUID guid;
virtual ~IInterface ()
{
}
};
// The GUID instance for IInterface
GUID IInterface::guid = {
// {94BE63E1-3B3A-4553-8591-823FBCD58F8A}
0x94be63e1, 0x3b3a4553, 0x8591823f, 0xbcd58f8a
};
// An example interface
struct IExample : public IInterface
{
static GUID guid;
virtual void sayHello () = 0;
};
// The GUID instance for IExample
GUID IExample::guid = {
// {C6B4D9CA-7F28-4e7c-870F-DC8F56AD7DC8}
0xc6b4d9ca, 0x7f284e7c, 0x870fdc8f, 0x56ad7dc8
};
// ---
#include <stdexcept> // for std::invalid_argument
// Our base class
struct IBaseClass
{
void getInterface (const GUID& iid, void** target)
{
if (target == 0) // can't write to 0 ...
throw std::invalid_argument ("target must be not null");
else // call through to the implementation
getInterfaceImpl (iid, target);
}
virtual ~IBaseClass ()
{
}
protected:
// Each derived class will implement this
virtual void getInterfaceImpl (const GUID& iid, void** target) = 0;
};
// Non-member call so you don't have to cast to void** on your own.
// Optionally, add a compile-time-check if T is derived from
// IInterface here!
template <typename T>
void getInterface (IBaseClass& object, const GUID& iid, T** target)
{
object.getInterface (iid, reinterpret_cast<void**>(target));
}
// ---
// Simple test class
class Test : public IBaseClass
{
public:
Test ()
: interface_ (this)
{
}
void say (const std::string& s)
{
std::cout << s << std::endl;
}
private:
// The interface, note that it is private
class TestInterface : public IExample
{
public:
TestInterface (Test* parent)
: parent_ (parent)
{
}
void sayHello ()
{
parent_->say ("Hello");
}
private:
Test* parent_;
};
TestInterface interface_;
// Our own implementation for getInterface
// Only knows IExample
void getInterfaceImpl (const GUID& iid, void** target)
{
if (iid == IExample::guid)
*target = &interface_;
else
throw std::runtime_error ("Unsupported interface");
}
};
// ---
int main ()
{
Test t;
IExample* e = 0;
getInterface (t, IExample::guid, &e);
e->sayHello ();
}
Related posts:
Wozu genau brauche ich nun die Klasse Test?
Ist die sozusagend die versteckte Implementierung von IExample? Und eine andere Implementierung von IExample könnte den “sayHello”-String auch via ein Webinterface erlangen?
Ist ja nur ein Beispiel.
Testist halt eine Klasse die Funktionalität X hat, aber auch das Interface Q unterstützt … die Idee ist dass es a.) noch woanders eine Klasse gibt die Q implementiert, z.B. sei Q sowas wie “ConvertibleToString” und b.) dass Q nen Haufen an Dependencies mit sich zieht (std::string, I/O Streams, etc.) die du nicht immer #includen willst wenn duTestbenutzt weil du das “ConvertToString” einmal im Jahr brauchst … und in die BasisklasseIBaseClasswillst du es schon gleich zweimal nicht reintun, weil eben nicht alle nach String konvertiert werden können.