Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a macro to create QObject derived classes

I'm trying to simplify (i.e. get rid of loads of boilerplate code) the creation of QObject wrapper classes that forward property access of other QObject derived classes.

To start small, I'm just trying it with one property:

// Sy_test.h - The wrapped class
class Sy_test : public QObject
{
    Q_OBJECT
    Q_PROPERTY( bool prop READ getProp WRITE setProp NOTIFY propChanged )

public:
    Sy_test( QObject* parent = nullptr ) :
        QObject{ parent },
        prop_{ false } {}

    bool getProp() const { return prop_; }

public slots:
    void setProp( bool value )
    {
        if ( value != prop_ ) {
            prop_ = value;
            emit propChanged( prop_ );
        }
    }

signals:
    void propChanged( bool value );

private:
    bool prop_;
};

// Sy_proxy.h - The wrapper generator
#define SY_PROXYPROPERTY( Type, Name, Getter, Setter, Notifier )\
private:\
    Q_PROPERTY( Type Name READ Getter WRITE Setter NOTIFY Notifier )\
\
public:\
    Type Getter() const { return target_->Getter(); }\
\
public slots:\
    void Setter( Type value ) { target_->Setter( value ); }\
\
signals:\
    void Notifier( Type value );\
\
private:\
    void setConnection()\
    {\
        connect( target_, &std::remove_pointer< decltype( target_ ) >::type::Notifier,\
                 this,    &std::remove_pointer< decltype( this    ) >::type::Notifier );\
    }

#define SY_PROXY( ProxyName, TargetType, Prop1 )\
class ProxyName : public QObject\
{\
    Q_OBJECT \
    Prop1 \
\
public:\
    ProxyName( TargetType* target ) :\
        target_{ target }\
    {\
        setConnection();\
    }\
\
    virtual ~ProxyName() {}\
\
private:\
    TargetType* target_;\
};

// This should create a Sy_test wrapper class called Sy_testProxy
SY_PROXY( Sy_testProxy,
          Sy_test,
          SY_PROXYPROPERTY( bool, prop, getProp, setProp, propChanged ) )

So the SY_PROXY macro should create a class called Sy_testProxy that carries a copy of the Sy_test::prop property with implementations that just forward requests/signals.

And it almost does. Looking at the post-preprocessor output (I'm using g++, so the .ii files), I can see the Sy_testProxy class is built and it's of the same form as the Sy_test class. However, I get an error:

../CppTest/Sy_proxy.h:47: Error: NOTIFY signal 'propChanged' of property 'prop' does not exist in class Sy_testProxy.
make: *** [moc_Sy_proxy.cpp] Error 1

So it looks like the moc is failing to parse my macro magic; although I'm not sure where as clearly the SY_PROXY macro is present (the error is coming from a class called Sy_testProxy), and SY_PROXYPROPERTY must be valid too (as the moc must have read the Q_PROPERTY macro from it). Can anyone see where I've gone wrong?

For the record: I hate macros like everyone else, but I've fell into using them due to the moc's aversion to templates and QObject virtual inheritance. This investigation was triggered because I had a collection of instances performing heavy calculations in a separate thread, but they drive QML representations. However QML does not allow connections/property bindings to objects outside of the main thread, so I've been forced into creating proxy object that live in the main thread. If anyone has a better idea, I'm very open to them!

like image 271
cmannett85 Avatar asked Sep 16 '15 15:09

cmannett85


People also ask

What is QObject macro?

QObject is the base class for all Qt classes, Q_OBJECT macro is used to enable meta-object features in classes and finally moc is a preprocessor that changes Q_OBJECT macro instances to C++ source code to enable meta object system mechanism in the class in which it is used.

What is QObject in Qt?

QObject is the heart of the Qt Object Model. The central feature in this model is a very powerful mechanism for seamless object communication called signals and slots. You can connect a signal to a slot with connect() and destroy the connection with disconnect().

What is a macro in C++?

Macros and its types in C/C++ A macro is a piece of code in a program that is replaced by the value of the macro. Macro is defined by #define directive. Whenever a macro name is encountered by the compiler, it replaces the name with the definition of the macro.

Where is Q_OBJECT defined?

The Q_OBJECT macro must appear in the private section of a class definition that declares its own signals and slots or that uses other services provided by Qt's meta-object system. The moc tool reads a C++ header file.


1 Answers

moc doesn't like macros very well. It expands them to some degree, but it fails when they get complicated¹.

You can try to replace signals: with public:² (i.e. manually expanding the signals macro), andtell moc that you want the function to be a signal by putting Q_SIGNAL in front of the function declaration.

Replace

signals:\
    void Notifier( Type value );\

with

public:\
    Q_SIGNAL void Notifier( Type value );\

¹: for some definition of complicated... I don't know when it fails, but I ran into some different problems in the past. From my experience my guess is that moc has problems when a macro body contains another macro, like signals in your example. But this is just a guess - maybe the kind of macros moc fails at is something else.

²: Before Qt 5, that used to be protected.

like image 54
leemes Avatar answered Sep 21 '22 02:09

leemes