QString::toStdString() のバグ

いきなりですが,次のコードはWindows環境でバグることがあります.

#include <QtCore/QString>
#include <string>
...
{
    ...
    // その1
    QString qtstr(tr("foobar"));
    std::string stdstr;
    stdstr = qtstr.toStdString(); // だめ
    ... // ここでヒープが壊れる
}

{
    ...
    // その2
    QString qtstr(tr("foobar"));
    std::string stdstr = qtstr.toStdString(); // だめ
    ...
} // ここでヒープが壊れる

実行すると,上のコメント部分で「ヒープが壊れています」と言われます.
Windows以外の環境はよく分かりません.

QStringは便利ではあるのですが,別のライブラリと連携しようとするとまず間違いなくstd::stringが必要になるわけで,これの変換が必要になる.
そこでQStringにtoStdString()なる関数が用意されていて,これを使えば楽に変換ができるよ!
……と運びたいところが,単にワナになってしまった,というオチですね.

以下のページでも言及されています(多分近いことをやろうとして悩まれています).

http://d.hatena.ne.jp/kasugano/20120302/1330684684

回避策はこう(上記ページからの引用).

#include <QtCore/QString>
#include <string>
...
{
    ...
    QString qtstr(tr("foobar"));
    std::string stdstr(qtstr.toAscii()); // OK
    ...
}

一度QByteArrayにデータを写して,それを引数にstd::stringを作るわけです.

toAscii()でコーデックは大丈夫なのか? と思ってマニュアルを見たら,どうやらQTextCodec::setCodecForCStrings()で指定したコーデックに変換されるようなので,ひとまず問題はなさそう.

QString Class | Qt 4.8

このバグ,英語のフォーラムを眺めても解決は見ていないようで,しばらくは対応されないような気がします.

(ちなみに当方のバージョンは4.8.0です)



……で,結局なぜバグるのか.
確認は面倒なので予想です(無責任ですみません).

ヒープバグが発生する典型的なケースとして「メモリを取得したインスタンス(EXEやDLLのこと)と解放するインスタンスが違う」というのがあります.
たとえばDLL側で取得したメモリを,DLLを呼び出したEXE側で解放しようとするとバグになります.

おそらくこのケースに見事にハマっているのではないか.
つまり,

  1. QString::toStdString() 内部で std::string の一時オブジェクトが生成される.
  2. 一時オブジェクトが戻り値としてQtCore*.dllから出てくる.
  3. いらなくなった時点で一時オブジェクトは破棄されるが,破棄するインスタンスがQtCore*.dllではないのでバグになる.

こういうこと.
サンプルその1では,operator =の実行が終わる時点で一時オブジェクトが破棄されるので,行を抜けるとエラー.
サンプルその2はコピーコンストラクタですが,この場合最適化で一時オブジェクトがそのままこちらのオブジェクトとなるので,スコープが終わる時点で破棄→エラーとなるわけです.

Qtを静的リンクしてインスタンスを同じにしてしまえば問題は起こらないような気がしますが,それはそれで(LGPL版が使えなくなるので)不便.

ううむ.



そういえば上の記事では「QString::toLatin1()でヒープバグが出た」的なことが書いてあるのですが,当方の環境では出ませんでした.
というより,toAscii()もtoLatin1()もQByteArrayを返すので,どちらか一方がおかしいというのは考え難いのですが……何か違う原因でしょうか.