I'm building a Qt wrapper for RtMidi. As starting point I created two separate classes QMidiOut and QMidiIn that wrap RtMidiOut and RtMidiIn.

I don't want to duplicate code for the common methods (e.g. openPort, closePort, etc), so I'm planning to derive QMidiOut and QMidiIn from a base class (QMidi) with all the the common methods (of RtMidi).

I cannot use template classes (Q_OBJECT doesn't allow that) and I can't create an RtMidi object in the base class, because its destructor is protected.

QMidiIn class:

#ifndef QMIDIIN_H
#define QMIDIIN_H

#include <QObject>
#include "QMidi.h"

class QMidiIn : public QMidi
{
    Q_OBJECT

public:
    explicit QMidiIn(QObject *parent = nullptr);
    ~QMidiIn();

private:
    RtMidiIn* rtMidiIn = nullptr;

    static void callback( double deltatime, std::vector< unsigned char > *message, void *userData );
};

#endif // QMIDIIN_H

QMidiOut class:

#ifndef QMIDIOUT_H
#define QMIDIOUT_H

#include <QObject>
#include "QMidi.h"

class QMidiOut : public QMidi
{
    Q_OBJECT

public:
    explicit QMidiOut(QObject *parent = nullptr);
    ~QMidiOut();

private:
    RtMidiOut* rtMidiOut = nullptr;
};

#endif // QMIDIOUT_H

QMidi base class

#ifndef QMIDI_H
#define QMIDI_H

#include <QObject>
#include "RtMidi.h"


class QMidi : public QObject
{
    Q_OBJECT

public:
    explicit QMidi(QObject *parent = nullptr) : QObject(parent) {};

    void openPort( unsigned int portNumber = 0, const QString &portName = "RtMidi" ); // common methods
    void closePort(); // common methods

protected:
    RtMidi* rtMidi = nullptr; 
};

My solution is to create the objects in the derived classes and then assign the pointer of the base class:

QMidiOut constructor:

rtMidiOut = new RtMidiOut();
rtMidi = static_cast<RtMidi*>(rtMidiOut);

QMidIn constructor:

rtMidiIn = new RtMidiIn();
rtMidi = static_cast<RtMidi*>(RtMidiIn);

There is a better way to achieve this? The goal is to write all the common methods in a base class.

6 Replies 6

Why is this question unsuitable for a normal Q&A? It looks like you are looking for an answer and not a discussion.

Is there a problem with your current solution? It seems pretty straightforward to me.

Another solution would be to remove the base class and maybe use static functions for your common functionality that take an RtMidi pointer as a parameter.

// Put all common functionality in a utility class
class QMidiUtils
{
    static void openPort( RtMidi *midiObj, ...);
    static void closePort( RtMidi *midiObj);
};

// Then when you call a common function, you just need to pass the pointer
void QMidiIn::someFunction()
{
    QMidiUtils::openPort(rtMidiIn, ...);
}

BTW, using templates are not totally banned. You can use CRTP with classes that inherit from QObject. I do this in some of my code.

RtMidi seems to be a base of RtMidiOut and RtMidiIn, so you don't need static_cast.

I'd probably use a std::unique_ptr<RtMidiOut> const for the owning subclass.

Consider something like this:

class QMidi : public QObject
{
    Q_OBJECT

protected:
    explicit QMidi(QObject *parent = nullptr)
      : QObject{parent}
    {}

    ~QMidi() override = default;

    virtual RtMidi* impl() = 0;

public:
    void openPort(unsigned portNumber = 0); // common methods
    void closePort(); // common methods
};

Then the subclasses can implement impl() to give the base access to the common functionality:

class QMidiIn : public QMidi
{
    Q_OBJECT

    std::unique_ptr<RtMidiIn> const rtMidiIn;

public:
    explicit QMidiIn(QObject *parent = nullptr)
      : QMidi{parent},
        rtMidiIn{std::make_unique<RtMidiIn>()}
    {}

    ~QMidiIn() override = default;

private:
    RtMidi* impl() override
    {
        return rtMidiIn.get();
    }
};

You might want both const and non-const impl() functions - I'll leave that as an exercise.

Don't use raw new and delete in modern C++ (at least not outside very low-level container classes etc). Use smart pointers.

Hey NoobNoob , I'm a Product Manager at Stack Overflow. We're gathering feedback on the new question type you used to ask this. If you have a moment, we'd love to hear how your experience was or what you would improve.

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.