3

I want to make a class similar to QPointer (or subclass it) which will emit a signal whenever the internal pointer gets changed.

Unfortunately I need to inherit QObject class to be able to emit signals and also I think I need to use templates to be able to store a specific type of pointer. But according to this question it is not possible to mix QObject with templates.

Any idea how to make my class which keeps track on internal pointer changes?

Honza Vojtěch
  • 685
  • 1
  • 7
  • 27
  • Just throwing an idea : create a class inheriting `QObject` that just have a function `emitPointerChange` wich actually emits a signal, and do a composition in the templated class of this custom-emitter-object. Then you would need to connect your slots to the `MyQPointer::m_emitter` pointer instead of the `QPointer` itself, is that possible ? – ymoreau Feb 28 '18 at 10:35

1 Answers1

3

You should use QPointer when you want to guard a QObject subclass instance. If the referenced pointer is deleted, QPointer is set to 0:

QLabel * label = new QLabel();
QPointer pointer = label;
delete label;
if(pointer) //false
{
//...

Since the held object is a QObject subclass, you can connect a slot to its destroyed signal, to track when it's deleted (and the QPointer is set to zero).

If a QPointer got reassigned, instead, no destroyed signal will be emitted

pointer = new QLabel();

the formerly held object is only left unguarded, but not deleted.

To track the pointer reset, you'd be better using QSharedPointer, instead. When the QSharedPointer held object is cleared or reset, and the internal reference count gets to zero, delete is called on the held object (and if it's a QObject subclass, the destroyed signal is emitted).

If you go for a DIY smart pointer, I suggest to use a QObject derived class for emitting signals and own an instance of it in your pointer class:

class Emitter : public QObject
{
    Q_OBJECT
public:
    Emitter() : QObject(0) {}
    void forward(void * p) { emit reset(p); }
signals:
    void reset(void *);
};

template <typename T>
class Pointer
{
public:
    Pointer() : instance(0){}
    void connect(QObject * receiver, const char * slot)
    {
        QObject::connect(&emitter, SIGNAL(reset(void*)), receiver, slot);
    }
    void set(T* t)
    {
        emitter.forward(instance);
        instance = t;
    }

    //...

private:
    Emitter emitter;
    T * instance;
};

Obviously, the above code is not intended as a smart pointer implementation, just an example to show how to emit signals from a class template.

To connect a slot (say from a Form class):

Pointer<QLabel> p;
p.connect(this, SLOT(pointerreset(void*)));

The void pointer passed to the slot is the T pointer held before reset. To simplify the unavoidable cast, you can add to the Pointer class a helper method like this:

T * cast(void * p) { return static_cast<T*>(p); }

This way, for a Pointer<QLabel> pointer, in a slot:

void Form::pointerreset(void * p)
{
    QLabel * label = pointer.cast(p);
    //...
}

Depending on your smart pointer implementation, you could consider not to use the old pointer at all and just emit a signal with no arguments. Maybe there's no reason to access the old object further, especially if it is about to be deleted.

p-a-o-l-o
  • 9,807
  • 2
  • 22
  • 35
  • In he meantime I found the exactly same solution :) The only pain is that signal `reset(void*)` does not provide templated pointer type, but only a generic `void *` pointer. In this case it is more comfortable to add a getter to `Pointer`class than always retype the pointer in the argument. Anyway, thank you for your solution! – Honza Vojtěch Feb 28 '18 at 10:52
  • 1
    When you access the held instance, after the signal has been received, are you 100% sure it refers to the old one and not to the newly set instance? I would pass the void pointer and cast it using the helper method I added to the answer. – p-a-o-l-o Feb 28 '18 at 11:18
  • Very nice solution, +1 – Honza Vojtěch Feb 28 '18 at 11:35