I'm trying to create a base class defining an interface for derived classes - say a simple audio wrapper class.
class AudioStreamBase
{
public:
virtual
~AudioStreamBase(void);
virtual void
open(const void * settings) = 0;
virtual void
start(void) = 0;
virtual void
stop(bool force) = 0;
virtual void
close(void) = 0;
virtual int
recover(int err) = 0;
virtual int
readFrames(void * buffer) = 0;
virtual int
writeFrames(void * buffer) = 0;
virtual void
printConfig(void) = 0;
};
As different implementations may accept different configuration parameters in Linux/Win/embedded system, I've defined the input parameter for open() as:
const void * settings
One possible implementation could be:
struct settingsA
{
int param1;
int param2;
...
};
class AudioStreamA : public AudioStreamBase
{
public:
...
void open(const void* settings) { settingsA * s = (settingsA) settings; ...};
...
}
However, this doesn't seem to be very C++ - I'm used to C a lot and I'm starting with C++ after several years again. Is there a better solution? I was thinking about templating the class or the method, but as the type is a struct, I won't be able to access the parameters. And building a complex struct construct to read it's parameters seems obscure for something simple like accessing few parameters in a struct.
You could use the CRTP to inject the derived class into the base. Then you could define a traits helper to define e.g. which settings have to be used for some derived class. Admittedly, a bit verbose but now your code is type-safe.
If you need the base class to be non-template, you could pull out everything except the open
into a non-template top base class.
class AudioStreamA;
struct settingsA;
template<class T>
struct AudioStreamTraits;
template<>
struct AudioStreamTraits<AudioStreamA> {
using Settings = settingsA;
};
template<class Derived>
class AudioStreamBase
{
protected:
using StreamTraits = AudioStreamTraits<Derived>;
using Settings = typename StreamTraits::Settings;
public:
virtual
~AudioStreamBase() = default;
virtual void
open(const Settings& settings) = 0;
//...
};
struct settingsA
{
int param1;
int param2;
};
class AudioStreamA : public AudioStreamBase<AudioStreamA>
{
using Base = AudioStreamBase<AudioStreamA>;
using Base::Settings;
public:
void open(const Settings& settings) override { };
};
int main() {
AudioStreamA s;
settingsA settings;
s.open(settings);
return 0;
}
See code example here
However, if you only need the settings in the derived class, you could simply replace the open
by a constructor in the derived class:
class AudioStreamBase
{
public:
virtual
~AudioStreamBase() = default;
// ....
};
struct settingsA
{
int param1;
int param2;
};
class AudioStreamA : public AudioStreamBase
{
public:
AudioStreamA(const settingsA& settings) { } ;
};