QSharedPointer не возвращает false

у меня сейчас что-то вроде этого

QSharedPointer<QMainWindow> cv;

Этот общий указатель используется как

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

Теперь, если я закрою QMainWindow, следующий код приведет к сбою приложения.

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

Мой вопрос: когда я закрыл объект cV QMainWindow (нажав кнопку x на ), почему следующее утверждение возвращает true

if(cV)

Как я могу заставить его возвращать false, если окно было закрыто?


person Rajeshwar    schedule 14.08.2014    source источник
comment
Я не уверен, что QScopedPointer подойдет для этого. QScopedPointer автоматически удаляется после окончания области видимости. указатель cV является переменной-членом класса, используемой различными методами. Я не уверен, как Scoped Pointer поможет мне здесь?   -  person Rajeshwar    schedule 15.08.2014
comment
также if(cv) и (cv.isNull()) одинаковы stackoverflow.com/questions/19925693/   -  person Rajeshwar    schedule 15.08.2014


Ответы (2)


Общий указатель волшебным образом не знает, когда вы удаляете объект, на который он указывает. Будет ошибкой вручную удалять объект, время жизни которого управляется общим указателем. Поскольку окно самоудаляется при закрытии, теперь вы получаете оборванные общие указатели.

Следовательно, вы не можете использовать QSharedPointer с виджетом Qt::WA_DeleteOnClose.

Что вам нужно, так это указатель, который отслеживает, существует ли еще виджет. Такой указатель QPointer, он делает именно то, что вам нужно. Этот указатель предназначен для сброса себя на ноль, когда QObject уничтожается.

Обратите внимание, что QPointer — это слабый указатель, он не удалит окно, когда оно выйдет за пределы области видимости.

Если вам нужен указатель-владелец, который позволяет удалить базовый QObject, есть способ сделать это:

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; }
};

Поскольку мы разрешаем удаление объекта с помощью других средств, кроме указателя, обращение к объекту из нескольких потоков является ошибкой. Если объект должен быть удален в другом потоке, возникает состояние гонки между проверкой нуля и использованием разыменованного объекта. Таким образом, QPointer или ScopedObjectPointer можно использовать только из потока объекта. Это прямо утверждается. Q_ASSERT становится недоступным в релизных сборках и не влияет на производительность.

person Kuba hasn't forgotten Monica    schedule 14.08.2014
comment
+1 за шаблон, который действует как неработающий объект QScopedPointer‹QPointer‹› › - person fxam; 15.08.2014
comment
Извините, я имею в виду QScopedPointer‹QPointer‹T› › ! - person fxam; 15.08.2014
comment
@fxam A QScopedPointer<QPointer<T>> — это указатель области действия, который управляет экземпляром QPointer. Конечно, это сработает, но толку от него будет мало: хранить QPointer<T> в куче особого смысла нет. - person Kuba hasn't forgotten Monica; 15.08.2014
comment
мы учимся каждый день. Я никогда не использовал QPointer и внезапно почувствовал себя глупо. - person UmNyobe; 15.08.2014
comment
@UmNyobe О, я тоже учусь каждый день. Не чувствовать себя глупо почти все время — признак того, что человек недостаточно учится! - person Kuba hasn't forgotten Monica; 15.08.2014
comment
QPointer полезен только в определенных обстоятельствах: только для объектов, живущих в том же потоке, где вы обращаетесь к QPointer. Я изменил класс, чтобы сделать его явным, что использование QPointer из других потоков всегда является ошибкой, так как между проверкой null и использованием разыменованного экземпляра возникает состояние гонки. - person Kuba hasn't forgotten Monica; 15.08.2014
comment
@KubaOber Я был в ситуации, когда QPointer очень удобен. - person UmNyobe; 15.08.2014

Объект, охраняемый QSharedPointer, предназначен для удаления самим QSharedPointer, когда все владельцы выходят за пределы области действия. В вашем случае вы позволяете QMainWindow удалять cV, когда пользователь его закрывает. QSharedPointer ничего не знает об этом инциденте и не будет автоматически устанавливать указатель на 0.

Ваше решение простое. Забудьте о QSharedPointer и просто используйте QPointer. QPointer автоматически установит указатель на 0, когда объект будет удален вами или QObject. Затем вы можете использовать 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;
};

Если вы хотите избежать delete в деструкторе и автоматически удаляет cV, когда он выходит за рамки, вы можете использовать KubarOber. ScopedQObjectPointer в другом ответе на этот вопрос:

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: я только что заметил, что вы используете родителя при создании cV: new QMainWindow(p). Хорошо, если p не равно нулю, тогда p удалит cV при удалении p. Так что нет необходимости удалять cV в деструкторе, даже если вы используете QPointer.

person fxam    schedule 14.08.2014
comment
обычно означает Не обычно. ВСЕГДА. Удаление того, на что указывает QSharedPointer или std::shared_ptr, является ошибкой. То, что вы защищаете, правильно, за исключением того, что это плохой совет не использовать для этого оболочку RTTI. Явное удаление в деструкторе означает, что вы используете не тот класс для задания. Хорошим ответом было бы написать такой класс, тем более что он тривиален. - person Kuba hasn't forgotten Monica; 15.08.2014
comment
@KubaOber, ОК удалено обычно :). Но если бы мы хотели избежать удаления в dtor, как бы мы удалили cV (который является QPointer), если пользователь не закрыл его? Вы хотели переместить cV в класс RAII, у которого есть delete в его dtor? - person fxam; 15.08.2014
comment
@KubaOber, хорошо, я думаю, что ваш ScopedQObjectPointer ответил на мой вопрос к вам выше :) - person fxam; 15.08.2014
comment
RAII означает, что вы создаете класс для управления ресурсом, а затем используете этот класс для управления им. delete в деструкторе означает, что вы где-то пропустили класс RAII. Явное управление ресурсами относится к классам, предназначенным для управления этими ресурсами. Вот почему у нас есть умные указатели: они делают только одну вещь, и делают это хорошо: они управляют куском памяти и живущим там экземпляром класса. - person Kuba hasn't forgotten Monica; 15.08.2014