Properties and returning const pointers



  • I've stumbled upon the following problem:

    Assume, I have a member variable that's a pointer, and I want to access it as a property.

    Something like this:

    @
    ...
    Q_OBJECT
    Q_PROPERTY(QObject* MyObject READ myObject WRITE setMyObject)

    ...

    const QObject* myObject() const
    {
    return m_pMyObject;
    }

    void setMyObject(QObject* arg)
    {
    m_pMyObject = arg;
    }

    ...

    QObject* m_pMyObject;
    @

    This does not compile. Why? Because the meta object system attempts to cast the const pointer returned by myObject() to QObject* instead of const QObject*.

    Now I like const correctness, and this issue made me think.

    First of all, I looked how Qt does it. And true enough, the getters are const while they return a non-const pointer, e.g.

    @
    QAction *activeAction() const;
    @

    Is this const correct? Formally, yes. The class only contains the pointer, and that cannot be changed when I give a non-const pointer to someone outside.

    What can be changed is the data behind the pointer, but the data is not formally part of the class.

    I would still prefer to fulfill the const correctness not only in the formal sense. When I hand out a pointer in a const method, it should be a const pointer, so the data behind it cannot be changed (except with const-cast).

    But how do I combine that with the property system? Is there a way to specify the return type of the getter should be const?
    Or do I have to create two getters, one for the property system, one for "regular" access?



  • You have a mix between const and non-const functions/members. Strong indication of architecture issue.

    Or you have all as const QObject* (member, set and get) or all as QObject*. The mix of both type could be done but not make sense. You could anyway use the returned type QObject* like a const QObject*.

    Now if you want to really protect the internal member you should have only the get method and of course all in const. In this case use the CONSTANT flag in Q_PROPERTY.



  • What's wrong if the setter is non-const, and the getter is const? After all, the setter is supposed to change the object, whereas the getter is not.

    The question is: When I call a setter that hands over an object pointer, do I expect the class to use the object pointer as it is, or to create a copy of the object?

    I say, the former, if alone because many object cannot be copied (see QObject), and implementing the latter would only work for objects that can be copied. That would lead to setters having two different behaviors within the same application. Not good.

    So I say the setter takes the pointer and uses it without copying the object.
    Handing over a const pointer there does not make sense, if the class has to be able to manipulate the data behind the pointer.

    I now have a setter with a non-const pointer. Why should that imply that the getter also has to return a non-const pointer? After all, a getter typically should not change "this".



  • But having your object const does not imply having objects you have pointers to as const. perhaps you are a mediator and modify the object you point to, but that does not change your object itself.

    The getter returns a value of the type of the property (which makes sense, as it is returned by value) and the value is copied. as it is a return by value and this is copied, there is no difference in const or not (it will always be copied).

    The problem comes into the game, as your value is a pointer (you could also use an intptr_t to store the pointer and return it, it would be logically the same).

    @
    class MyClass
    {
    Q_OBJECT
    Q_PROPERTY(intptr_t MyObject READ myObject WRITE setMyObject)
    public:
    intptr_t myObject() const
    {
    return (intptr_t)m_pMyObject;
    }

    void setMyObject(intptr_t arg)
    {
        m_pMyObject = (QObject*)arg;
    }
    

    private:
    QObject* m_pMyObject;
    }
    @

    so talking in patterns and templates, it would be:

    @
    Q_PROPERTY(<type> MyObject <readFunc> WRITE <writeFunc>
    @
    with:
    @
    template <class T> T readFunc() const; // where const is optional
    template <class T> void writeFunc(T val); // where val could also be a reference
    @



  • You can set a non-const QObject in the setter and keep that pointer for later (aka pointer aliasing). So the Object can be manipulated anyways. "Protecting" it via a const pointer in the getter is a security means, that adds only fake security.



  • I am aware even a const getter does not provide real security against change.

    It's more like a protection against some common cases of unintended change. It also documents that someone calling the getter should not change the object behind the pointer.

    The intention here is not to provide security that can't be broken from the outside, but to provide security that's unlikely to be broken by accident.

    But I do concede that having the same type as getter and setter does make sense from the property's standpoint.



  • You're right with the accidental change in principle and I'm with you about adding more const than less in principle :)

    It's a special case with Qts property system here and the way moc is and the meta method calling is designed, and I fear we have to live with it for the time being.



  • Well, there's of course the following approach:

    @...
    Q_OBJECT
    Q_PROPERTY(QObject* MyObject READ myObjectMutable WRITE setMyObject)

    ...

    const QObject* myObject() const
    {
    return m_pMyObject;
    }

    QObject* myObjectMutable()
    {
    return m_pMyObject;
    }

    void setMyObject(QObject* arg)
    {
    m_pMyObject = arg;
    }

    ...

    QObject* m_pMyObject;
    @

    It would enable the property system to work, but in most cases, simply accessing the property using myObject() would return a const pointer.

    Elegant it is not.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.