簡體   English   中英

為什么 qt 應用程序總是新的小部件而不是在堆棧上?

[英]why qt application always new widgets instead of on stack?

那里。 我從 qt 看到了這個例子: https://doc.qt.io/qt-5/qtwidgets-widgets-calculator-example.html ,想知道為什么 qt 總是用 new 創建小部件而不是在堆棧上分配

Button *Calculator::createButton(const QString &text, const char *member)
{
    Button *button = new Button(text);
    connect(button, SIGNAL(clicked()), this, member);
    return button;
}

在堆棧上創建小部件更快,在這個問題中: QT-specific difference of stack vs. heap attributes? Mike的回答說create on stack完全沒問題,但是為什么官方文檔大多用new呢?

這很簡單:因為即使在這個 function scope 完成並且QObject不可復制之后,您仍希望小部件仍然存在。 以下代碼不起作用,因為您無法復制 object。

Button Calculator::createButton(const QString &text)
{
    Button button(text); // allocated on stack, it is OK so far...
    return button; // nope, you cannot copy that! it does not compile.
}

以下代碼也不起作用,因為您將傳遞已刪除的 object。

Button *Calculator::createButton(const QString &text)
{
  Button button(text); // allocated on stack, it is OK so far...
  return &button; // ... returning pointer, still OK...
  // Oh no! End of scope here, the object gets murdered now. 
  // whoever holds the pointer to it, holds a dead, invalid object. 
}

object 在 scope 的末尾被刪除,您將傳遞一個指向已釋放的指針,因此 memory 無效。

你提到的例子

class Widget2 : public QWidget
{
Q_OBJECT
public:
    Widget2(QWidget* parent = nullptr);

private:
    QPushButton button;
};

並不一定意味着button是在堆棧上創建的。 這取決於Widget2的實例是如何創建的。 如果它在堆棧上,那么button也在堆棧上。 如果它是堆上的new ed,那么button也在堆上。 與聲明button並將其保留為指針的“正常”情況的不同之處在於,在上面的示例中,它們在一次分配中一起分配。 是的,它的速度可以忽略不計,但這只是一個過早的優化。 小部件的分配永遠不會成為您的瓶頸,相信我。 順便提一句。 Qt 中的大多數對象都是使用 PIMPL 慣用法來實現的,以保持二進制兼容性,因此它們無論如何都會在內部分配堆上的私有對象。

是的,您可以將button保留為非指針成員。 這是合法的。 但這是不明智的。 這種方法存在一個問題:您必須#include header 中的所有子窗口小部件,並且您不能轉發聲明它們。 這肯定會使您的編譯時間更長。 但這不是主要問題,您可以稍等片刻。 更大的問題是您正在引入構建依賴項,這會降低您的代碼的可組合性。 想象一下,您將自定義小部件拆分為多個庫 - 那么您在標頭中對其他模塊可見的#include的所有內容也需要對這些模塊可見。 這很快就會變得難以管理,因為您將無法隱藏/封裝實現細節。 您的小部件使用某些按鈕這一事實當然是一個實現細節。 只需在您的 header 中保留盡可能少的細節即可。 保留一個指針(可以向前聲明)肯定比保留一個非指針成員(必須是#include d.)負擔更小,而且如果你保留一個指針。 那么你可以使用替換原則,這個指針實際上可以指向任何子類實例,在小玩具應用程序或學校作業中這無關緊要,但是當你設計一個非常大的應用程序時,它有很多模塊和子模塊組織成庫,或者當你是編寫供其他人使用的庫。 那么這些事情真的很重要。 這使得可維護性好的代碼和完全不可維護的代碼之間存在差異。

在我的實踐中,我只遇到過兩個實際在堆棧上分配小部件的用例。 首先是在main()中創建它們時,您可以在其中編寫:

int main() {
  QApplication app;
  MainWindow w; // it is really on the stack
  w.show();
  return app.exec();
  // here is the main window automatically destroyed
}

另一種情況是,當您使用exec()打開一個帶有阻塞事件循環的模式對話框時。

void showSomeMessage() {
  QMessageBox box; // yes, we should set a parent here, for sake of simplicity I omitted it
  // etc. set text and button here
  box.exec(); // blocking here, we are waiting for user interaction
  // end of scope - we are done here, we do not need the message box any more 
  // so it is perfectly fine it gets automatically deleted here
}

這些情況非常好。 實際上,正如您在這兩種情況下看到的阻塞exec()一樣,它們非常相似。

結論:最好通過指針聲明成員QObject s 或QWidget s,並始終在您的標頭中轉發聲明它們,而不是#include它們的標頭。 通過這種方式,您可以設計出更具可組合性、可維護性和面向未來的代碼。 您不會在小型玩具項目中看到任何顯着差異,但是當您的代碼庫變大時,您將受益於這種良好做法。 更好的習慣是通過QPointer (一個特殊的 Qt 弱指針)來保存它們,它有兩個好處:1)它會自動初始化為nullptr和 2)當對象被銷毀時,指針會自動設置為nullptr所以你可以輕松檢查它是否還活着。 另外QPointer提供到原始指針的隱式轉換,因此它不會影響您編寫代碼的方式。 這是一個非常方便的 class。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM