Search code examples
c++templatessubclassing

C++ base class method with different input struct argument for derived classes


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.


Solution

  • 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)  { } ;
    };