1

I currently have something like this

QSharedPointer<QMainWindow> cv;

This shared pointer is used as

cV = QSharedPointer<QMainWindow>(new QMainWindow(p));
cV->setAttribute(Qt::WidgetAttribute::WA_DeleteOnClose);
cV->show();

Now if I close the the QMainWindow then the following code makes the app crash

if(cV)
    cV->close(); //This pointer is no longer valid.

My Question is when I closed thecV QMainWindow Object (by clicking on the x button of the ) why is the following statement returning true

if(cV)

How could i make it return false if the Window has been closed?

demonplus
  • 5,613
  • 12
  • 49
  • 68
Rajeshwar
  • 11,179
  • 26
  • 86
  • 158
  • I am not sure if QScopedPointer would be suitable for this. QScopedPointer is deleted automatically after a scope ends. the cV pointer is a class member variable being used by different methods. I am not sure how Scoped Pointer would help me here ? – Rajeshwar Aug 14 '14 at 20:28
  • also if(cv) and (cv.isNull()) are the same http://stackoverflow.com/questions/19925693/qt-difference-between-qsharedpointerisnull-and-operator – Rajeshwar Aug 14 '14 at 20:32

2 Answers2

5

A shared pointer doesn't magically know when you delete the object it points to. It is an error to manually delete an object whose lifetime is managed by a shared pointer. Since the window self-deletes when it gets closed, you now get dangling shared pointers.

Ergo, you can't use QSharedPointer with a widget that is Qt::WA_DeleteOnClose.

What you need is a pointer that tracks whether the widget still exists. Such pointer is QPointer, it does exactly what you need. That pointer is designed to reset itself to zero when a QObject gets destroyed.

Note that QPointer is a weak pointer, it won't delete the window when it goes out of scope.

If you need an owning pointer that allows deletion of the underlying QObject, there's a way to do it:

template <typename T> class ScopedQObjectPointer {
  Q_DISABLE_COPY(ScopedQObjectPointer)
  QPointer<T> m_ptr;
  inline void check() const {
    Q_ASSERT(m_ptr && (m_ptr->thread() == 0
             || m_ptr->thread() == QThread::currentThread()));
  }
public:
  explicit ScopedQObjectPointer(T* obj = 0) : m_ptr(obj) {}
  ScopedQObjectPointer(ScopedQObjectPointer &&other) : m_ptr(other.take()) {}
  ~ScopedQObjectPointer() { check(); delete m_ptr; }
  operator T*() const { check(); return m_ptr; }
  T & operator*() const { check(); return *m_ptr; }
  T * operator->() const { check(); return m_ptr; }
  T * data() const { check(); return m_ptr; }
  T * take() { check(); T * p = m_ptr; m_ptr.clear(); return p; }
  void reset(T * other) { check(); delete m_ptr; m_ptr = other; }
  operator bool() const { check(); return m_ptr; }
};

Since we allow the deletion of the object through other means than the pointer, it is an error to access the object from multiple threads. If the object were to be deleted in another thread, there's a race condition between the null check and the use of dereferenced object. Thus, a QPointer, or a ScopedObjectPointer can only be used from object's thread. This is explicitly asserted. The Q_ASSERT becomes a no-op in release builds and has no performance impact there.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
1

The object guarded by QSharedPointer is meant to be deleted by QSharedPointer itself when all owners go out of scope. In your case, you are letting QMainWindow to delete cV when user closes it. QSharedPointer has no knowledge about that incident and will not set the pointer to 0 automatically.

Your solution is simple. Forget about QSharedPointer and just use QPointer. QPointer will automatically set a pointer to 0 when the object is deleted by yourself or by QObject. You can then use if (cV).

class foo {  

public:
  ~foo() {      
    delete cV; // delete cV if cV is not 0.
  }

  void showMainWindow() {
    if ( ! cV) {
      cV = new QMainWindow();
      cV->setAttribute(Qt::WidgetAttribute::WA_DeleteOnClose);
    }
    cV->show();
  }
  void closeMainWindow() {
    if (cV) { // cV is 0 if it is already deleted when user closes it
      cV->close();
    }
  }

private:
  QPointer<QMainWindow> cV;
};

If you want to avoid delete in destructor and automatically deletes cV when it goes out of scope, you can use KubarOber's ScopedQObjectPointer in another answer to this question:

class foo {  

public:
  ~foo() {      
  }

  void showMainWindow() {
    if ( ! cV) {
      cV.reset(new QMainWindow());
      cV->setAttribute(Qt::WidgetAttribute::WA_DeleteOnClose);
    }
    cV->show();
  }
  void closeMainWindow() {
    if (cV) { // cV is 0 if it is already deleted when user closes it
      cV->close();
    }
  }

private:
  ScopedQObjectPointer<QMainWindow> cV;
};

EDIT: I just noticed that you use a parent when creating cV: new QMainWindow(p). Well if p is not null then p will delete cV when p is deleted. So there's no need to delete cV in destructor even if you use QPointer.

Community
  • 1
  • 1
fxam
  • 3,874
  • 1
  • 22
  • 32
  • "is usually meant to" **Not usually. ALWAYS**. It is an **error** to delete what's pointed to by `QSharedPointer` or `std::shared_ptr`. What you advocate is correct, except that it's bad advice not to use an RTTI wrapper for this. An explicit delete in a destructor means you're not using the right class for the job. A good answer would be to actually write such a class, especially that it's trivial. – Kuba hasn't forgotten Monica Aug 14 '14 at 23:58
  • @KubaOber, OK removed **usually** :). But if we were to avoid delete in dtor, how do we delete cV (which is a QPointer) if user didn't close it? Did you mean to move cV to a RAII class which has a **delete** in its dtor? – fxam Aug 15 '14 at 00:02
  • @KubaOber, ok I think your ScopedQObjectPointer answered my question to you above :) – fxam Aug 15 '14 at 00:23
  • 1
    RAII means that you make a *class* to manage the resource, and then use that class to manage it. A `delete` in destructor means that you've missed a RAII class somewhere. Explicit resource management belongs in classes that are *dedicated* to managing those resources. That's why we have owning smart pointers: they do just one thing, and do it well: they manage a chunk of memory and the class instance living there. – Kuba hasn't forgotten Monica Aug 15 '14 at 08:18