Qt開発者必見: QListへの要素追加、emplaceBack() vs push_back()
この関数シグネチャは、C++11で導入された「可変引数テンプレート (variadic templates)」と、C++11/17で強化された「インプレース構築 (in-place construction)」の概念を組み合わせたものです。QtのQList
クラスにこのemplaceBack
メソッドが存在すると仮定して説明します。
template <typename... Args>
- これにより、関数は様々なコンストラクタを持つオブジェクトを柔軟に構築できるようになります。
- 例えば、
emplaceBack(10, "hello", 3.14)
のように呼び出すと、Args
はint
,const char*
,double
のような型に展開されます。 - これは「可変引数テンプレート」と呼ばれるものです。
Args
はテンプレートパラメータパックであり、任意の数の異なる型の引数を受け取ることができます。
QList<T>::reference
- つまり、新しくリストの末尾に構築された要素への参照が返されます。これにより、その要素をすぐに操作したり、値を取得したりすることができます。
- これは関数の戻り値の型です。
QList<T>::reference
は通常、T&
(T型への参照)を指します。
QList::emplaceBack(Args&&... args)
- 具体的には、
args
として渡された引数が直接QList
が保持するT
型オブジェクトのコンストラクタに渡され、メモリ上で直接構築されます。これは特に、コンストラクタが複雑な初期化を必要とするオブジェクトや、コピー/ムーブコストが高いオブジェクトの場合にパフォーマンス上のメリットをもたらします。 - 従来の
push_back()
関数とは異なり、emplaceBack()
は新しい要素をリストに追加する際に、一時オブジェクトの生成とコピー(またはムーブ)を避けます。 Args&&... args
は「転送参照 (forwarding references)」または「ユニバーサル参照 (universal references)」と呼ばれるもので、受け取った引数をその値カテゴリ(左辺値か右辺値か)を保ったまま、次の関数(この場合はT
のコンストラクタ)に転送します。- これは
QList
クラスのメンバー関数で、名前が示す通り「リストの末尾 (back) に要素を配置 (emplace) する」ためのものです。
例(emplaceBackが存在すると仮定した場合)
#include <QList>
#include <QDebug>
class MyClass {
public:
MyClass(int id, const QString& name) : m_id(id), m_name(name) {
qDebug() << "MyClass(int, QString) constructor called: " << m_id << ", " << m_name;
}
MyClass(const MyClass& other) : m_id(other.m_id), m_name(other.m_name) {
qDebug() << "MyClass copy constructor called: " << m_id;
}
// ... 他のメンバー関数
private:
int m_id;
QString m_name;
};
int main() {
QList<MyClass> myList;
// push_back の場合:MyClassオブジェクトが一時的に作成され、その後リストにコピー(またはムーブ)される
// myList.push_back(MyClass(1, "First")); // MyClassのコンストラクタとコピー/ムーブコンストラクタが呼ばれる
// emplaceBack の場合:引数が直接MyClassのコンストラクタに転送され、リストの内部で直接構築される
// もしemplaceBackが存在すれば、以下のように書ける
// MyClass& ref = myList.emplaceBack(2, "Second"); // MyClassのコンストラクタのみが呼ばれる
// qDebug() << "Emplaced object ID: " << ref.getId(); // 仮にgetId()が存在するとして
return 0;
}
- emplaceBack(Args&&... args)
args
で指定された引数を使用して、リストの内部で直接T
型オブジェクトを構築します。- 一時オブジェクトの生成とコピー/ムーブのオーバーヘッドを削減できます。
- push_back(const T& value) / push_back(T&& value)
value
として渡されたオブジェクトをコピー(またはムーブ)してリストに追加します。- 先にオブジェクトを構築してから
push_back
に渡す必要があります。
QList::emplaceBack()
は、C++の高度な機能である可変引数テンプレートとインプレース構築を利用しているため、いくつかの一般的なエラーやトラブルシューティングのポイントが存在します。
コンパイルエラー: "'emplaceBack' is not a member of 'QList'"
原因
最も一般的な原因は、使用しているQtのバージョンがemplaceBack
をサポートしていないことです。emplaceBack
はQt 6以降で導入された機能であり、Qt 5系では通常利用できません。また、古いバージョンのQt 6を使用している場合や、ビルドシステム(CMake, qmakeなど)の設定が正しくない場合も発生することがあります。
トラブルシューティング
- プロジェクトのクリーンと再ビルド
ビルドキャッシュが古い情報を持っている可能性があるため、プロジェクトを完全にクリーンしてから再ビルドしてみてください。 - ヘッダーのインクルードを確認する
<QList>
が正しくインクルードされていることを確認してください。 - ビルド設定を確認する
- CMake
find_package(Qt6 REQUIRED COMPONENTS Core)
のように、Qt 6のコンポーネントが正しく指定されていることを確認してください。Qt 5とQt 6を混在させてビルドしている場合に問題が発生することがあります。 - qmake
.pro
ファイルにQT += core
が含まれていることを確認してください。
- CMake
- Qtのバージョンを確認する
Qt 6以降を使用していることを確認してください。Qt Creatorのプロジェクト設定やqmake -v
コマンドなどで確認できます。
コンパイルエラー: "要素の型 T の適切なコンストラクタが見つからない"
原因
emplaceBack
は、渡された引数(Args&&... args
)をT
型のコンストラクタに直接転送します。したがって、指定された引数の組み合わせに一致するT
型のコンストラクタが存在しない場合、コンパイルエラーが発生します。
例
class MyClass {
public:
MyClass(int id) : m_id(id) {} // intを引数にとるコンストラクタのみ
private:
int m_id;
};
QList<MyClass> myList;
// これはOK
myList.emplaceBack(10);
// これはエラー: QStringを引数にとるコンストラクタがない
// myList.emplaceBack("hello");
トラブルシューティング
- 暗黙的な変換
必要であれば、引数がT
のコンストラクタに暗黙的に変換可能であることを確認してください。しかし、emplaceBack
の目的は直接構築であるため、不必要な暗黙的変換は避けるのが理想です。 - Tのコンストラクタを確認する
emplaceBack
に渡している引数の型と数に合致するT
型のコンストラクタが存在するか確認してください。
コンパイルエラー: "要素の型 T がコピー/ムーブ不可能である" または "削除された関数を使用している"
原因
emplaceBack
はインプレース構築を目的としていますが、QList
の内部実装(特にリサイズや内部的なデータ再配置の際)で、要素のコピーコンストラクタやムーブコンストラクタ、あるいは代入演算子が依然として必要となる場合があります。特に、QList
の暗黙的共有の仕組みは、要素がコピー可能であることを要求することが多いです。
std::vector
の場合、emplace_back
は通常、T
のコンストラクタがあればよく、コピー/ムーブコンストラクタは不要ですが、QList
の古い設計や特定の条件下ではコピー/ムーブが必須となるケースがあるようです。
例
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete; // コピーコンストラクタを削除
NonCopyable& operator=(const NonCopyable&) = delete; // コピー代入演算子を削除
// QListの内部動作によっては、ムーブコンストラクタ/代入演算子も問題になる可能性
// NonCopyable(NonCopyable&&) = default;
// NonCopyable& operator=(NonCopyable&&) = default;
};
QList<NonCopyable> myList;
// これがコンパイルエラーになる場合がある
// myList.emplaceBack();
トラブルシューティング
- std::vectorを検討する
もし、本当にコピー/ムーブできない型を扱いたいのであれば、C++標準ライブラリのstd::vector
の方がemplace_back
においてより厳密な保証を提供しているため、そちらの利用を検討するのも良い選択です。Qt 6のQVector
は内部的にQList
のエイリアスとなっていますが、振る舞いが異なる場合があります。 - ポインタやスマートポインタを使用する
QList<T>
の代わりにQList<T*>
やQList<QSharedPointer<T>>
などを使用して、T
オブジェクト自体ではなく、そのポインタを格納することを検討します。これにより、QList
が扱うのはポインタのコピー/ムーブになり、T
オブジェクト自体のコピー/ムーブ制約を回避できます。 - コピー/ムーブ可能にする
可能な場合は、T
型に適切なコピーコンストラクタ、ムーブコンストラクタ、代入演算子を実装するか、= default
で自動生成されるようにします。
ランタイムエラー: メモリ破損 (クラッシュ、未定義動作)
原因
emplaceBack
自体が直接メモリ破損を引き起こすことは稀ですが、以下のような状況で関連する問題が発生する可能性があります。
- デバッグ/リリースバイナリの混在
稀に、Qtのデバッグビルドとリリースビルドのライブラリが混在することで、ヒープメモリの管理が不整合を起こし、クラッシュにつながることがあります。 - T型の不正な状態
emplaceBack
で構築されたT
オブジェクトが不正な状態になり、その後のアクセスでクラッシュする場合。これはemplaceBack
の引数の問題というよりも、T
のコンストラクタの実装の問題です。 - コンストラクタ内部での例外
T
のコンストラクタが例外をスローした場合、QList
の内部状態が破損する可能性があります。Qtコンテナは通常、例外安全性を保証しません。
トラブルシューティング
- ビルド設定の確認
デバッグビルドとリリースビルドでQtライブラリが正しくリンクされているか確認します。特に、CMake
やqmake
の自動設定に任せず、手動でパスを指定している場合に発生しやすいです。 - AddressSanitizer (ASan) などのツール
メモリ関連の問題を検出するために、GCCやClangに搭載されているAddressSanitizerなどのツールを利用することを検討します。 - Tのコンストラクタの堅牢性
T
のコンストラクタが、与えられた引数に対して常に有効なオブジェクトを構築できることを確認します。特に、リソースの確保や外部システムの呼び出しを行う場合は、エラー処理を適切に行う必要があります。 - デバッガを使用する
クラッシュが発生した場合は、デバッガを使ってスタックトレースを確認し、どのコードパスで問題が発生しているかを特定します。
コンパイル時間の増加
原因
可変引数テンプレートは、異なる引数の組み合わせごとにテンプレートがインスタンス化されるため、emplaceBack
を多用するとコンパイル時間が増加する可能性があります。これはエラーではありませんが、開発体験に影響を与える可能性があります。
- Pimplイディオム
複雑なクラスをQList
に格納する場合、Pimplイディオム(実装をプライベートなポインタに隠す)を適用することで、ヘッダーファイルでの依存関係を減らし、コンパイル時間を短縮できる場合があります。 - これは避けられないトレードオフです。 パフォーマンスのメリットとコンパイル時間のバランスを考慮する必要があります。
基本的な使用例
最もシンプルなケースでは、引数なしのコンストラクタを持つオブジェクトを構築します。
#include <QList>
#include <QDebug>
// シンプルなクラス定義
class MyClass {
public:
MyClass() {
qDebug() << "MyClass() default constructor called.";
}
// コピーコンストラクタとムーブコンストラクタはデモンストレーションのために定義
MyClass(const MyClass& other) {
qDebug() << "MyClass copy constructor called.";
}
MyClass(MyClass&& other) {
qDebug() << "MyClass move constructor called.";
}
};
int main() {
QList<MyClass> myList;
qDebug() << "--- push_back() の場合 ---";
// push_back() を使う場合、一時オブジェクトが作成され、その後コピーまたはムーブされる
myList.push_back(MyClass()); // MyClass() -> デフォルトコンストラクタ, push_backへのムーブ
qDebug() << "リストのサイズ: " << myList.size();
qDebug() << "\n--- emplaceBack() の場合 ---";
// emplaceBack() を使う場合、MyClassのコンストラクタが直接QListの内部で呼ばれる
myList.emplaceBack(); // MyClass() -> デフォルトコンストラクタのみ
qDebug() << "リストのサイズ: " << myList.size();
return 0;
}
出力例(環境によって多少異なる場合があります)
--- push_back() の場合 ---
MyClass() default constructor called.
MyClass move constructor called.
リストのサイズ: 1
--- emplaceBack() の場合 ---
MyClass() default constructor called.
リストのサイズ: 2
この例からわかるように、push_back(MyClass())
ではMyClass()
で一時オブジェクトが作成され、その後QList
内部へのムーブ構築が発生しているのに対し、emplaceBack()
ではMyClass()
コンストラクタが直接呼ばれているだけです。これにより、ムーブ(あるいはコピー)のオーバーヘッドが削減されます。
複数の引数を持つコンストラクタへの適用
emplaceBack()
の真価は、複数の引数を持つコンストラクタを持つオブジェクトを効率的に構築できる点にあります。
#include <QList>
#include <QDebug>
#include <QString>
class Product {
public:
// コンストラクタA: IDと名前
Product(int id, const QString& name) : m_id(id), m_name(name), m_price(0.0) {
qDebug() << "Product(int, QString) constructor called for ID:" << m_id << ", Name:" << m_name;
}
// コンストラクタB: ID、名前、価格
Product(int id, const QString& name, double price) : m_id(id), m_name(name), m_price(price) {
qDebug() << "Product(int, QString, double) constructor called for ID:" << m_id << ", Name:" << m_name << ", Price:" << m_price;
}
// デモンストレーションのためのコピー/ムーブコンストラクタ
Product(const Product& other) : m_id(other.m_id), m_name(other.m_name), m_price(other.m_price) {
qDebug() << "Product copy constructor called for ID:" << m_id;
}
Product(Product&& other) : m_id(other.m_id), m_name(std::move(other.m_name)), m_price(other.m_price) {
qDebug() << "Product move constructor called for ID:" << m_id;
}
int id() const { return m_id; }
QString name() const { return m_name; }
double price() const { return m_price; }
private:
int m_id;
QString m_name;
double m_price;
};
int main() {
QList<Product> products;
qDebug() << "--- push_back() の場合 ---";
// IDと名前のみの製品をpush_backで追加
products.push_back(Product(101, "Laptop")); // Product(int, QString)コンストラクタ + ムーブコンストラクタ
// ID、名前、価格の製品をpush_backで追加
products.push_back(Product(102, "Mouse", 25.50)); // Product(int, QString, double)コンストラクタ + ムーブコンストラクタ
qDebug() << "products size:" << products.size();
qDebug() << "\n--- emplaceBack() の場合 ---";
// IDと名前のみの製品をemplaceBackで追加
products.emplaceBack(103, "Keyboard"); // Product(int, QString)コンストラクタのみ
// ID、名前、価格の製品をemplaceBackで追加
products.emplaceBack(104, "Monitor", 299.99); // Product(int, QString, double)コンストラクタのみ
qDebug() << "products size:" << products.size();
qDebug() << "\n--- リストの内容 ---";
for (const Product& p : products) {
qDebug() << "ID:" << p.id() << ", Name:" << p.name() << ", Price:" << p.price();
}
return 0;
}
出力例
--- push_back() の場合 ---
Product(int, QString) constructor called for ID: 101 , Name: "Laptop"
Product move constructor called for ID: 101
Product(int, QString, double) constructor called for ID: 102 , Name: "Mouse" , Price: 25.5
Product move constructor called for ID: 102
products size: 2
--- emplaceBack() の場合 ---
Product(int, QString) constructor called for ID: 103 , Name: "Keyboard"
Product(int, QString, double) constructor called for ID: 104 , Name: "Monitor" , Price: 299.99
products size: 4
--- リストの内容 ---
ID: 101 , Name: "Laptop" , Price: 0
ID: 102 , Name: "Mouse" , Price: 25.5
ID: 103 , Name: "Keyboard" , Price: 0
ID: 104 , Name: "Monitor" , Price: 299.99
この例では、emplaceBack()
が引数の型と数に応じてProduct
クラスの適切なコンストラクタを直接呼び出していることが明確に示されています。push_back()
の場合と比較して、余分なムーブコンストラクタの呼び出しが発生していません。これにより、特に構築コストが高いオブジェクトの場合にパフォーマンスの向上が期待できます。
QList::emplaceBack()
は、新しく構築された要素への参照(QList<T>::reference
、つまりT&
)を返します。これにより、要素をリストに追加した直後にその要素を操作できます。
#include <QList>
#include <QDebug>
#include <QString>
class Item {
public:
Item(const QString& name, int quantity) : m_name(name), m_quantity(quantity) {
qDebug() << "Item constructor called for:" << m_name;
}
void increaseQuantity(int amount) {
m_quantity += amount;
qDebug() << "Increased quantity of" << m_name << "to" << m_quantity;
}
QString name() const { return m_name; }
int quantity() const { return m_quantity; }
private:
QString m_name;
int m_quantity;
};
int main() {
QList<Item> shoppingCart;
// emplaceBackの返り値を利用して、構築直後に要素を操作
Item& apple = shoppingCart.emplaceBack("Apple", 5);
apple.increaseQuantity(3); // 構築したばかりの"Apple"オブジェクトのquantityを増やす
shoppingCart.emplaceBack("Banana", 10);
qDebug() << "\n--- ショッピングカートの内容 ---";
for (const Item& item : shoppingCart) {
qDebug() << "Item:" << item.name() << ", Quantity:" << item.quantity();
}
return 0;
}
出力例
Item constructor called for: "Apple"
Increased quantity of "Apple" to 8
Item constructor called for: "Banana"
--- ショッピングカートの内容 ---
Item: "Apple" , Quantity: 8
Item: "Banana" , Quantity: 10
この例では、emplaceBack()
が返した参照apple
を通じて、リストに要素が追加された直後にその要素のincreaseQuantity()
メソッドを呼び出しています。これは、要素を追加してから再度リストから検索する手間を省き、コードをより簡潔かつ効率的に記述できる点で便利です。
主に以下の3つの代替方法が考えられます。
QList::push_back(const T& value)
またはQList::push_back(T&& value)
QList::append(const T& value)
またはQList::append(T&& value)
- ポインタまたはスマートポインタを格納する
それぞれの方法について詳しく見ていきましょう。
QList::push_back(const T& value) または QList::push_back(T&& value)
これはemplaceBack
が導入される前から最も一般的な方法であり、現在でも広く使われています。
使い方
push_back(T&& value)
(ムーブ):T
型の一時オブジェクト(右辺値)をムーブしてリストの末尾に追加します。これにより、元のオブジェクトが破棄される際にリソースが効率的に転送されます。push_back(const T& value)
(コピー):T
型の既存のオブジェクトをコピーしてリストの末尾に追加します。
例
#include <QList>
#include <QDebug>
#include <QString>
class MyData {
public:
MyData(int id, const QString& name) : m_id(id), m_name(name) {
qDebug() << "MyData constructor called: ID=" << m_id << ", Name=" << m_name;
}
MyData(const MyData& other) : m_id(other.m_id), m_name(other.m_name) {
qDebug() << "MyData copy constructor called: ID=" << m_id;
}
MyData(MyData&& other) noexcept : m_id(other.m_id), m_name(std::move(other.m_name)) {
qDebug() << "MyData move constructor called: ID=" << m_id;
}
// デストラクタ
~MyData() {
qDebug() << "MyData destructor called: ID=" << m_id;
}
int id() const { return m_id; }
QString name() const { return m_name; }
private:
int m_id;
QString m_name;
};
int main() {
QList<MyData> myList;
qDebug() << "--- push_back(T&&) の場合 ---";
// 一時オブジェクトを直接push_backに渡す (ムーブセマンティクスが働く)
myList.push_back(MyData(1, "Alice")); // MyDataのコンストラクタとムーブコンストラクタが呼ばれる
qDebug() << "リストサイズ: " << myList.size();
qDebug() << "\n--- push_back(const T&) の場合 ---";
// 既存のオブジェクトをコピーして渡す (コピーセマンティクスが働く)
MyData bob(2, "Bob"); // MyDataのコンストラクタ
myList.push_back(bob); // コピーコンストラクタが呼ばれる
qDebug() << "リストサイズ: " << myList.size();
qDebug() << "\n--- リストの内容 ---";
for (const MyData& data : qAsConst(myList)) { // qAsConstは読み取り専用アクセスを保証し、不必要なdetachを防ぐ
qDebug() << "ID:" << data.id() << ", Name:" << data.name();
}
// main関数終了時にmyListの要素とbobのデストラクタが呼ばれる
return 0;
}
メリット
- 非常にシンプルで理解しやすい。
- Qt 4/5/6の全てのバージョンで利用可能。
デメリット
emplaceBack
と比較して、一時オブジェクトの生成と、その後のコピーまたはムーブのオーバーヘッドが発生します。特に、コンストラクタの呼び出しコストが高い、またはコピー/ムーブのコストが高い(例えば、大きなデータ構造を持つ)オブジェクトの場合、パフォーマンスに影響が出る可能性があります。
QList::append(const T& value) または QList::append(T&& value)
append()
は、セマンティクス的にはpush_back()
とほぼ同じです。Qtのコンテナは歴史的にappend()
という名前をよく使っており、push_back()
はC++標準コンテナとの互換性のために導入されたものです。
使い方
push_back()
と同様に、コピーまたはムーブによって要素をリストの末尾に追加します。
例
#include <QList>
#include <QDebug>
#include <QString>
// MyDataクラスは上記と同じとする
int main() {
QList<MyData> myList;
qDebug() << "--- append(T&&) の場合 ---";
myList.append(MyData(3, "Charlie")); // MyDataのコンストラクタとムーブコンストラクタが呼ばれる
qDebug() << "リストサイズ: " << myList.size();
qDebug() << "\n--- append(const T&) の場合 ---";
MyData david(4, "David");
myList.append(david); // コピーコンストラクタが呼ばれる
qDebug() << "リストサイズ: " << myList.size();
qDebug() << "\n--- リストの内容 ---";
for (const MyData& data : qAsConst(myList)) {
qDebug() << "ID:" << data.id() << ", Name:" << data.name();
}
return 0;
}
メリット・デメリット
push_back()
と全く同じです。コードの好みによってどちらを使うか選ばれます。
ポインタまたはスマートポインタを格納する
もし、T
型のオブジェクトが非常に大きく、コピーやムーブのコストが問題となる場合、またはオブジェクトのライフサイクルをより細かく制御したい場合、QList<T*>
やQList<QSharedPointer<T>>
のようなポインタ型のリストを使用することを検討できます。
使い方
QList
自体にはポインタを格納し、実際のT
オブジェクトはヒープに動的に割り当てます。
例 (生ポインタ - 非推奨、メモリ管理に注意)
#include <QList>
#include <QDebug>
#include <QString>
// MyDataクラスは上記と同じとする
int main() {
QList<MyData*> myList; // ポインタのリスト
qDebug() << "--- 生ポインタの場合 ---";
// MyDataオブジェクトはヒープに直接構築され、そのポインタがリストに追加される
MyData* erin = new MyData(5, "Erin"); // コンストラクタのみ
myList.push_back(erin); // ポインタのコピー
qDebug() << "リストサイズ: " << myList.size();
MyData* frank = new MyData(6, "Frank"); // コンストラクタのみ
myList.append(frank); // ポインタのコピー
qDebug() << "リストサイズ: " << myList.size();
qDebug() << "\n--- リストの内容 ---";
for (const MyData* data : qAsConst(myList)) { // ポインタ経由でアクセス
qDebug() << "ID:" << data->id() << ", Name:" << data->name();
}
qDebug() << "\n--- メモリ解放 (重要!) ---";
// 手動でメモリを解放する必要がある
qDeleteAll(myList); // QListのヘルパー関数。リスト内の全ポインタが指すオブジェクトを削除し、リストをクリア
myList.clear(); // オブジェクトは削除されたが、リスト自体はポインタを保持したままなのでクリアが必要
return 0;
}
例 (スマートポインタ - 推奨)
#include <QList>
#include <QDebug>
#include <QString>
#include <QSharedPointer> // Qtのスマートポインタ
// MyDataクラスは上記と同じとする
int main() {
QList<QSharedPointer<MyData>> myList; // QSharedPointerのリスト
qDebug() << "--- スマートポインタの場合 ---";
// QSharedPointer<MyData>を構築し、その中でMyDataオブジェクトを直接構築
// QSharedPointer::create() はemplaceBackのような働きをする
myList.push_back(QSharedPointer<MyData>::create(7, "Grace")); // MyDataのコンストラクタのみ
qDebug() << "リストサイズ: " << myList.size();
// もう一つの方法 (伝統的なnewとQSharedPointerのコンストラクタ)
myList.append(QSharedPointer<MyData>(new MyData(8, "Heidi"))); // MyDataのコンストラクタのみ
qDebug() << "リストサイズ: " << myList.size();
qDebug() << "\n--- リストの内容 ---";
for (const QSharedPointer<MyData>& dataPtr : qAsConst(myList)) { // QSharedPointer経由でアクセス
qDebug() << "ID:" << dataPtr->id() << ", Name:" << dataPtr->name();
}
qDebug() << "\n--- メモリは自動で解放される ---";
// main関数のスコープを抜けるときにQSharedPointerが自動的にメモリを解放する
return 0;
}
メリット
- ライフサイクル管理の容易さ (スマートポインタの場合)
QSharedPointer
やQScopedPointer
を使用すれば、メモリ管理のほとんどを自動で行ってくれるため、メモリリークのリスクを大幅に減らせます。 - ポリモーフィズムのサポート
異なる派生クラスのオブジェクトを同じリストに格納できます(ただし、基本クラスのポインタ型でリストを宣言する必要があります)。 - コピー/ムーブのコストを回避
QList
が扱うのはポインタ(またはスマートポインタオブジェクト)のコピー/ムーブだけになり、T
オブジェクト自体のコピー/ムーブは発生しません。
デメリット
- キャッシュ効率の低下
オブジェクトがヒープ上の異なる場所に分散して配置されるため、連続したメモリブロックに格納される値型に比べてキャッシュの効率が低下する可能性があります。 - 生ポインタの管理は危険
生ポインタを使用する場合、手動でのメモリ解放が必要となり、メモリリークやダングリングポインタのリスクが高まります。Qtアプリケーションでは、生ポインタの代わりにQSharedPointer
やQScopedPointer
の使用が強く推奨されます。 - 間接参照のオーバーヘッド
要素にアクセスするたびにポインタのデリファレンスが必要になります。
QList::emplaceBack()
が利用できない(または使いたくない)場合、最も一般的な代替手段はpush_back()
またはappend()
です。これらは値型を直接扱いますが、一時オブジェクトの生成とコピー/ムーブのコストがかかる可能性があります。