1. 初めに
※ 今更聞けないシグナル・スロット総整理の記事が2000PVを突破しました。この場を借りてお礼を述べさせていただきます。
さて、C++経験者が、Qt/C++をこれから学習しようとして入門書などのサンプルを見たときに、最初に気になるのは、メモリの解放は大丈夫なのか?ということではないでしょうか。
生ポインタでnewしているのに、deleteしているコードが見当たらないからです。
そこで最初に知るのが、QObjectは自身のthisを親(クラスの親という意味ではない)QObjectにparent設定すれば、親が解放された時に子も一緒に開放してくれる仕組みがあるからdelelteしなくても大丈夫
という説明です。
この説明で気になるのが、子は自動的に解放されるとして、親はどうやって解放するのか?ということです。
もっと言うと、Qt Creatorのプロジェクト作成ウィザードで作成されるmain()関数のコードは問題がないのでしょうか?
この辺りのことについて、自分なりの解が出ましたので、記事にさせていただきます。
2. 検証
プロジェクト作成ウィザードで、「Qtウィジェットアプリケーション選択→規定クラスをQWidgetに変更」してプロジェクトを作成します。
すると、main.cppは下記のようになります。
#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をスタックに確保するはメモリの使い方がよくない。ヒープに確保すべきだ、と言って下記のように書き換えたとします。
#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クラスのデストラクタが呼ばれるか」という観点で動作確認してみます。
#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のコードはこのままではまずいようです。
下記のように修正します。
#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に追加するとどうなるでしょうか?
#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の学習意欲が削がれる人がいる気がします・・・
そんな時は本記事を読んで、やる気が復活することを祈ります。