10
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Qtのmain関数内のウィジェット作成場所は・・・スタック?ヒープ?

Last updated at Posted at 2018-04-29

1. 初めに

今更聞けないシグナル・スロット総整理の記事が2000PVを突破しました。この場を借りてお礼を述べさせていただきます。

さて、C++経験者が、Qt/C++をこれから学習しようとして入門書などのサンプルを見たときに、最初に気になるのは、メモリの解放は大丈夫なのか?ということではないでしょうか。
生ポインタでnewしているのに、deleteしているコードが見当たらないからです。
そこで最初に知るのが、QObjectは自身のthisを親(クラスの親という意味ではない)QObjectにparent設定すれば、親が解放された時に子も一緒に開放してくれる仕組みがあるからdelelteしなくても大丈夫
という説明です。
この説明で気になるのが、子は自動的に解放されるとして、親はどうやって解放するのか?ということです。
もっと言うと、Qt Creatorのプロジェクト作成ウィザードで作成されるmain()関数のコードは問題がないのでしょうか?
この辺りのことについて、自分なりの解が出ましたので、記事にさせていただきます。

2. 検証

プロジェクト作成ウィザードで、「Qtウィジェットアプリケーション選択→規定クラスをQWidgetに変更」してプロジェクトを作成します。
すると、main.cppは下記のようになります。

main.cpp(スタック型1)
#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}

Widgetクラスは、QWidgetを継承したクラスです。
UIプログラミングの経験者がこのコードを見ると、画面のクラスであるWidgetをスタックに確保するはメモリの使い方がよくない。ヒープに確保すべきだ、と言って下記のように書き換えたとします。

main.cpp(ヒープ型1)
#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget *w = new Widget;
    w->show();

    return a.exec();
}

まずは、この2つのコードをアプリ終了時に「Widgetクラスのデストラクタが呼ばれるか」という観点で動作確認してみます。

widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    qDebug() << "~Widget";
    delete ui;
}

のようにログを仕込んで、動作させます。

すると、スタック型1はデストラクタが呼ばれますが、ヒープ型1は呼ばれません。
ヒープ型1のコードはこのままではまずいようです。
下記のように修正します。

main.cpp(ヒープ型2)
#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget *w = new Widget;
    w->setAttribute(Qt::WA_DeleteOnClose);
    w->show();

    return a.exec();
}

Qt::WA_DeleteOnCloseという属性を設定すること、ウィンドウがクローズした時に自動的に解放されます。
これを設定することで、デストラクタが呼ばれました。
では、仮にこの属性をスタック型1に追加するとどうなるでしょうか?

main.cpp(スタック型2)
#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.setAttribute(Qt::WA_DeleteOnClose);
    w.show();

    return a.exec();
}

これを実行すると

~Widget
~Widget
プログラムが突然終了しました。
The process was ended forcefully.

といういうように2重解放になってしまいます。
ここまでのサンプルを整理すると、

Qt::WA_DeleteOnCloseなし Qt::WA_DeleteOnCloseあり
スタック型 OK NG(2重解放)
ヒープ型 NG(デストラクタが呼ばれない) OK

となります。

3. 結論

結論を言うと、プロジェクト作成ウィザードで作成されるスタック型1で通常は問題ない。
スタック枯渇が気になる場合は、ヒープ型にしてQt::WA_DeleteOnCloseをセットする、です。

4. 書籍のサンプルコードはどうなっている?

2018/4/29現在、Qt4/C++の本は3冊出ています。(Qt5はなし。。。)

(1)入門 Qt4プログラミング(オライリー社)
(2)実践 Qt4プログラミング(オライリー社)
(3)Qtプログラミング入門(工学社)

これらのサンプルを見ると、
(1)はヒープ型1とスタック型1が混ざっている(hello worldはヒープ型1)
(2)スタック型1
(3)スタック型1

怖いのが、(1)のP.4にヒープ型1を採用している理由として、
「例をなるべくシンプルに保つために、main関数の最後でdeleteしていない。
このサンプルのように小さいアプリケーションでは、この程度のメモリリークはさほど問題になりません。」
のようなことが書かれているのですが、
どんなコードでもリークするコードを提示するのはダメでしょう!!!
とつっこみたくなる。
上記のオライリー本の説明を見て、Qtの学習意欲が削がれる人がいる気がします・・・
そんな時は本記事を読んで、やる気が復活することを祈ります。

10
1
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?