私の質問は基本的に、 Qt コンテナとしていつ選択するかQVector
、いつ選択するかということです。私がすでに知っていることは次のとおりです。QList
- Qt ドキュメント:QList クラス
ほとんどの目的には、QList が適切なクラスです。インデックス ベースの API は、QLinkedList のイテレータ ベースの API よりも便利で、アイテムをメモリに格納する方法により、通常は QVector よりも高速です。また、実行可能ファイル内のコードも少なくなります。
非常に人気のあるこのQ&Aにも同様のことが書かれています:QVector と QList. また、QList も優先されます。
しかし、最近の Qt World Summit 2015 で KDAB は「QList が有害な理由」を発表しましたが、基本的には次のようになります。
QList ではなく、Q_DECLARE_TYPEINFO を使用してください。
私が理解している限りでは、QList
ほとんどすべての型について、ヒープに新しい要素を割り当てるときに は非効率的であるということです。新しい要素を追加するたびに、 が呼び出されnew
(要素ごとに 1 回)、これは と比較して非効率的ですQVector
。
QVector
これが、私が今理解しようとしている理由です。デフォルトのコンテナーとしてどれを選択すべきでしょうか?
ベストアンサー1
Qt はQList
「何でも屋」として宣伝されていますが、その言葉のもう半分は「何一つマスターしていない」です。QList
リストの両端に追加することを計画していて、その前後にスペースを予約するため、ポインターよりも大きくない場合は、Qt は良い候補だQList
と思います。使用する正当な理由に関しては、それだけですQList
。
QList
は自動的に「大きな」オブジェクトをポインタとして保存し、ヒープ上にオブジェクトを割り当てます。これは、宣言方法QVector<T*>
や動的割り当て方法を知らない初心者にとっては良いことかもしれません。これは必ずしも良いことではなく、場合によってはメモリ使用量が膨れ上がり、余分な間接参照が追加されるだけです。私の意見では、ポインタであれインスタンスであれ、何が欲しいのかを明示的に指定するのが常に良い考えです。ヒープ割り当てが必要な場合でも、オブジェクトを一度構築してからヒープ上にコピー構築するよりも、自分で割り当ててポインタをリストに追加する方が常に良いです。
QList
Qt は、たとえば の子を取得するときや子を検索するときなど、オーバーヘッドが発生する多くの場所で を返しますQObject
。この場合、最初の要素の前にスペースを割り当てるコンテナーを使用するのは意味がありません。これは、既に存在するオブジェクトのリストであり、先頭に追加されるものではないためです。また、メソッドがないのもあまり好きではありませんresize()
。
64 ビット システムで、サイズが 9 バイトでバイト アラインメントが設定されたオブジェクトがある状況を想像してください。これは「多すぎる」ため、QList
代わりに 8 バイトのポインター + 低速ヒープ割り当てのための CPU オーバーヘッド + ヒープ割り当てのためのメモリ オーバーヘッドが使用されます。メモリは 2 倍使用され、アクセスのための追加の間接参照により、宣伝されているようなパフォーマンス上の利点はほとんど得られません。
なぜ突然「デフォルト」のコンテナになれないのかという点についてはQVector
、レースの途中で馬を乗り換えることはできないので、これはレガシーなことです。Qt は古いフレームワークであり、多くのものが廃止されているにもかかわらず、広く使用されているデフォルトを変更することは、多くのコードを壊したり、望ましくない動作をしたりすることなく、常に可能であるとは限りません。良くも悪くも、QList
Qt 5 全体にわたってデフォルトであり続ける可能性があり、次のメジャー リリースでもおそらく同様です。スマート ポインターが必須になり、誰もがプレーン ポインターがいかに悪いか、そして決して使用すべきではないかを嘆いているのに、Qt が「ダム」ポインターを何年も使い続けるのも同じ理由です。
そうは言っても、誰も強制はしていないあなたQList
あなたのデザインに使用してくださいQVector
。あなたのデフォルトのコンテナ。私自身は をどこにも使用せずQList
、 を返す Qt 関数では、QList
にデータを移動するための一時的なものとしてのみ使用しますQVector
。
さらに、これはあくまでも私の個人的な意見ですが、Qt にはパフォーマンスやメモリ使用効率、使いやすさの面で必ずしも意味をなさない設計上の決定が数多くあると感じています。全体的に、最善の方法だからではなく、それが彼らのやり方だからという理由で、自分たちのやり方を推奨したがるフレームワークや言語がたくさんあります。
最後になりましたが、重要なことです:
ほとんどの場合、QList が適切なクラスです。
結局のところ、これをどう理解するかが問題です。私の意見では、この文脈における「正しい」とは、「最良」や「最適」を意味するのではなく、「最良でなくても十分」という意味で「十分」を意味します。特に、さまざまなコンテナ クラスとその動作について何も知らない場合はそうです。
ほとんどの場合、QList で十分です。
まとめると:
QList
長所
- ポインタのサイズよりも大きいオブジェクトを先頭に追加するつもりはありません。いくつかの前方のスペース
- リストの中央にポインタよりも(かなり)大きいオブジェクトを挿入するつもりです(
QVector
明示的なポインタを使用して同じことを簡単に実現でき、余分なコピーが不要なため、ここでは寛大にしています)。リストのサイズを変更すると、オブジェクトは移動されず、ポインタのみが移動されるためです。
QList
欠点
resize()
メソッドを持たないのreserve()
は微妙な罠です。有効なリストのサイズは増加しません。インデックスアクセスが機能しても、UBカテゴリに分類され、そのリストを反復処理することもできません。- オブジェクトがポインタより大きい場合、余分なコピーとヒープ割り当てを行う。これは、オブジェクトのアイデンティティが重要な場合にも問題になる可能性がある。
- ポインタよりも大きいオブジェクトにアクセスするために追加の間接参照を使用する
- 最後の2つによりCPU時間とメモリ使用量のオーバーヘッドがあり、キャッシュにもあまり適していない
- 「検索」の戻り値として使用する場合、その前に追加したり、追加したりする可能性は低いため、追加のオーバーヘッドが発生します。
- インデックス アクセスが必須である場合にのみ意味があり、最適な先頭追加および挿入パフォーマンスを得るには、リンク リストの方が適している可能性があります。
短所は長所をわずかに上回っています。つまり、「普段使い」であればQList
許容できるかもしれませんが、CPU 時間やメモリ使用量が重要な要素となる状況では絶対に使用しないでください。全体的に、 は、QList
ユースケースに最適なストレージ コンテナー (通常は 、 、 ) を考慮したくない場合に、怠惰で不注意な使用に最適ですQVector<T>
(QVector<T*>
ここQLinkedList
では Qt について話しているので「STL」コンテナーは除外します。Qt コンテナーは移植性が高く、高速になる場合もあり、間違いなくより簡単でクリーンに使用できますが、std
コンテナーは不必要に冗長です)。