Qt開発者必見: QListへの要素追加、emplaceBack() vs push_back()

2025-06-06

この関数シグネチャは、C++11で導入された「可変引数テンプレート (variadic templates)」と、C++11/17で強化された「インプレース構築 (in-place construction)」の概念を組み合わせたものです。QtのQListクラスにこのemplaceBackメソッドが存在すると仮定して説明します。

template <typename... Args>

  • これにより、関数は様々なコンストラクタを持つオブジェクトを柔軟に構築できるようになります。
  • 例えば、emplaceBack(10, "hello", 3.14)のように呼び出すと、Argsint, 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が含まれていることを確認してください。
  • 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ライブラリが正しくリンクされているか確認します。特に、CMakeqmakeの自動設定に任せず、手動でパスを指定している場合に発生しやすいです。
  • 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つの代替方法が考えられます。

  1. QList::push_back(const T& value) または QList::push_back(T&& value)
  2. QList::append(const T& value) または QList::append(T&& value)
  3. ポインタまたはスマートポインタを格納する

それぞれの方法について詳しく見ていきましょう。

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;
}

メリット

  • ライフサイクル管理の容易さ (スマートポインタの場合)
    QSharedPointerQScopedPointerを使用すれば、メモリ管理のほとんどを自動で行ってくれるため、メモリリークのリスクを大幅に減らせます。
  • ポリモーフィズムのサポート
    異なる派生クラスのオブジェクトを同じリストに格納できます(ただし、基本クラスのポインタ型でリストを宣言する必要があります)。
  • コピー/ムーブのコストを回避
    QListが扱うのはポインタ(またはスマートポインタオブジェクト)のコピー/ムーブだけになり、Tオブジェクト自体のコピー/ムーブは発生しません。

デメリット

  • キャッシュ効率の低下
    オブジェクトがヒープ上の異なる場所に分散して配置されるため、連続したメモリブロックに格納される値型に比べてキャッシュの効率が低下する可能性があります。
  • 生ポインタの管理は危険
    生ポインタを使用する場合、手動でのメモリ解放が必要となり、メモリリークやダングリングポインタのリスクが高まります。Qtアプリケーションでは、生ポインタの代わりにQSharedPointerQScopedPointerの使用が強く推奨されます。
  • 間接参照のオーバーヘッド
    要素にアクセスするたびにポインタのデリファレンスが必要になります。

QList::emplaceBack()が利用できない(または使いたくない)場合、最も一般的な代替手段はpush_back()またはappend()です。これらは値型を直接扱いますが、一時オブジェクトの生成とコピー/ムーブのコストがかかる可能性があります。