Using an abstract base class is one way to implement the idea of "
public interfaces with hidden implementations" (a form of information hiding).
The underlying principle isn't really about using one particular implementation of a public interface/hidden implementation, it's about the fact that information hiding / encapsulation are good things, and you should try to reduce dependencies within the physical structure of your code-base.
Yes, hiding the implementations of your classes is a good idea -- it keeps the public header file (which forms the interface) very clean, which makes your code-base compile faster and also makes it less confusing for the user.
However, I personally avoid using abstract-base-classes (classes with virtual methods) to implement this, because I personally like to avoid using virtual/polymorphism except where it's really required. That's just a stylistic choice.
e.g.
I posted in your previous thread with an implementation of
implementation-hiding that I commonly use.
At one of my previous jobs, we wrote everything in C++, but all of the engine's public interfaces were written in C, so our public headers looked like:
typedef struct {} IMutex;
IMutex* CreateMutex();
void ReleaseMutex(IMutex*);
void LockMutex(IMutex*);
and the private implementation could be anything, e.g.
class Mutex { void Lock() {...} };
extern "C" IMutex* CreateMutex() { return (IMutex*)new Mutex; }
extern "C" void ReleaseMutex(IMutex* m) { delete (Mutex*)m; }
extern "C" void LockMutex(IMutex* m) { ((Mutex*)m)->Lock(); }
^^This is still "an interface", even though it's not done in the "typical" C++ style of an
ABC.
In C++, it's also quite common to see the
PIMPL idiom used instead of ABC's for this purpose.
I know virtual methods adds an additional overhead
The actual overhead differs greatly depending on the situation. In some situations, it's basically zero overhead (
e.g. a non-frequently used class, like a Mutex, compiled on x86), in other situations, the overheads are so high that it's practically unusable (
e.g. a frequently used class, like float4, compiled for a NUMA SPU).
[edit]As another example of how many different ways there are to implement "interfaces", here's another interesting method:
// N.B. implementor doesn't know about the interface or the user, it's completely decoupled!
class Implementor
{
int z;
public:
Implementor(int z) : z(z) {}
void OnEvent(int x, int y)
{
printf( "%d, %d, %d\n", x, y, z );
}
};
// This is basically a decoupled vtable that lets us make the above act as if it used virtual functions / inherited an ABC
struct Interface
{
typedef void (FnOnEvent)(void*, int x, int y);
void* userData;
FnOnEvent* onEvent;
template<class T> static void s_OnEvent(void* o, int x, int y) { ((T*)o)->OnEvent(x, y); }
template<class T> static Interface Bind(T& o)
{
Interface value =
{
&o,
&s_OnEvent<T>,
};
return value;
}
};
// this does something using the above interface, so we can plug the first concrete class into it
class UserOfInterface
{
Interface i;
public:
UserOfInterface( Interface i ) : i(i) {}
void Event() { i.onEvent(i.userData, 1, 2); }
};
//like this:
Implementor widget(3);
UserOfInterface user( Interface::Bind(widget) );
user.Event(); //prints 1, 2, 3