I'm working on my first non-trivial project using the Qt Framework and to help maintain consistency across documents and to ensure I don't forget some small requirement I've decided to make a template document demonstrating the member functions, macros, etc. needed to subclass QObject. I also want to be able to fully utilize the Meta Object system. Am I missing anything or misunderstanding anything? Also feel free to give any general C critiques as necessary.
I'm especially concerned with the issue of whether or not to include a copy constructor. Is that only necessary for classes not derived from QObject?
Requirements (links are to the document I got the requirements from)
- Public Default Constructor (link)
- Public Copy Constructor (see 1) (but conflicts with this)
- Public Destructor (see 1)
- Use the Q_OBJECT Macro (link)
- Ensure Properties Are Accessible with the Q_PROPERTY(...) Macro (link)
- Declare the Type with Q_DECLARE_METATYPE(T) in the Header (link) (link)
- Declare any Enums Used with Q_ENUM(E) in the Header (link)
Template Header
// Include guards
#ifndef CLASSNAME_H
#define CLASSNAME_H
// Include statements
#include <QObject>
#include <T.h>
// Enum definition
enum E{
E0,
E1,
E2
};
// Q_ENUM Macro
Q_ENUM(E)
// Class declaration
class ClassName : public QObject
{
// Q_OBJECT Macro
Q_OBJECT
// Q_PROPERTY Macros
Q_PROPERTY(T* memberA READ memberA WRITE setMemberA NOTIFY memberAChanged)
Q_PROPERTY(int memberB READ memberB WRITE setMemberB NOTIFY memberBChanged)
Q_PROPERTY(E memberC READ memberC WRITE setMemberC RESET resetMemberC)
public:
// Constructors and Destructor
ClassName(QObject *parent = nullptr);
ClassName() = default;
ClassName(const ClassName&) = default;
~ClassName();
// Getters
T* memberA() const {return m_memberA;}
int memberB() const {return m_memberB;}
E memberC() const {return m_memberC;}
// Setters
void setMemberA(T* newA);
void setMemberB(int newB);
void setMemberC(E newC);
signals:
void memberAChanged(T*);
void memberBChanged(int);
public slots:
void resetMemberC();
private slots:
private:
// Data Members
T* m_memberA;
int m_memberB;
E m_memberC;
};
// Meta Object Type Declaration
Q_DECLARE_METATYPE(TypeName);
// End include guard
#endif // CLASSNAME_H
The source file to accompany this header would likely be trivial, so I won't include it here. Though if anyone thinks it would be helpful to demonstrate some requirement or functionality, I'd be happy to write it out.
CodePudding user response:
As Jeremy Friesner suggested, the requirements are not that strict. The situation is more like this:
- If your class uses signals and/or slots, it must both have the Q_OBJECT macro and be derived from QObject,
- If it only uses other meta-object functionality, such as Q_PROPERTY declarations, it can use the Q_GADGET macro and need not be derived from QObject,
- If it doesn't need any of that, but should still be compatible with Qt templates like QVariant, it should be declared with Q_DECLARE_METATYPE. The same applies to enums and Q_ENUM.
A Q_PROPERTY/Q_INVOKABLE interface is only really needed if you need your class to be interoperable with QML code.
As for your other question, yes that is an important difference between QObjects and non-QObjects. The metatype must be copyable, which is why that is required of types you manually declare as metatypes, and also why the system instead uses pointers for QObject types, which are not copyable themselves. A minimal QObject declaration could start like this:
#ifndef CLASSNAME_H
#define CLASSNAME_H
#include <QObject>
// Enums in the global namespace cannot be registered; they must be enclosed
// in a class and registered with Q_ENUM, or in a namespace declared as
// Q_NAMESPACE and registered with Q_ENUM_NS
class ClassName : public QObject
{
Q_OBJECT
public:
// Default constructor, with explicit specifier to prevent accidental
// implicit conversion from a QObject*
explicit ClassName(QObject *parent = nullptr);
};
// ClassName* is automatically declared as a metatype
#endif // CLASSNAME_H
In general I'd recommend the "rule of zero": if possible, do not declare a destructor, nor any copy and move operations, and leave them to the compiler. Of course I also recommend all of the other guidelines there, if you have the time!