Abstract (virtual) interfaces allow you to explicitly declare a set of methods which a child class must implement in order to compile and be used with other parts of the code. This works great for most problems, except with one drawback, you must pass/store pointers/references to the interface class rather than the class itself. This is because the compiler has no way of knowing the size of the implementing type at compile time.
However, this may not be suitable for some applications. For example consider a Mutex
class (assume it's similar to std::mutex
). I want to be able to instantiate and use instances of Mutex
directly throughout my code (i.e., I don't want to create them externally and pass them into constructors via pointers/references). For example:
class MyClass
{
private:
Mutex mutex_;
};
But, I also want my code to be portable/independent of the specific Mutex
implementation (e.g., Mutex
could be implemented for Linux, Windows, FreeRTOS, etc.).
This is the same problem you would come across when using templates like so:
template <typename Mutex>
class MyClass
{
private:
Mutex mutex_;
};
Except I don't want everything to be a template (that gets very messy and slow to compile very quickly).
The Mutex
class has an implicit interface. In other words, as long as a version of Mutex
exists at compile-time that has the same set of methods, it will work without issue.
I am wondering, is there a way to somehow explicitly (in code) declare this interface?
One option I know of is the PIMPL pattern. You could implement Mutex
as so:
//Mutex.hpp
class Mutex
{
class Impl;
public:
void lock();
void unlock();
private:
Impl* impl_;
};
//std/Mutex.cpp
#include <Mutex.hpp>
#include <mutex>
class Mutex::Impl
{
public:
void lock() { mtx_.lock(); }
void unlock() { mtx_.unlock(); }
private:
std::mutex mtx_;
};
Mutex::Mutex():
impl_(new Impl)
{
}
void Mutex::lock()
{
impl_.lock();
}
void Mutex::unlock()
{
impl_.unlock();
}
Note: I'm not sure if my syntax is 100% correct, this is just meant to be an illustrative example.
Every implementation would then have a Mutex.cpp file like above with its appropriate implementation to which the lock/unlock calls are forwarded.
Unfortunately, since you have to dynamically allocate Impl
, it's basically the same as if I had just passed in a reference in the first place and it's not suitable for applications which do no/should not use the heap (i.e., embedded systems).
The only other "solution" I have is something like the following:
//Mutex.hpp
#include <portable/Mutex.hpp>
/*
* The Mutex class must have the following interface
*
* class Mutex
* {
* public:
* void lock();
* void unlock();
* };
*
*/
This is just a "wrapper" file which includes the implementation and documents what the interface should be.