【C++】コンテナ入門!初心者から上級者まで役立つ7つの実例を解説

C++のコンテナを学ぶイメージC++
この記事は約17分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

C++プログラミング言語は、その柔軟性と高性能により、幅広いアプリケーション開発において重要な役割を果たしています。

特に、C++のコンテナはデータの保管や操作において中心的な概念となっており、効率的なプログラミングには欠かせない要素です。

この記事では、C++におけるコンテナの基本から、その使い方、さまざまな種類のコンテナとその特徴、適切なコンテナの選び方について、初心者から上級者までが理解できるように丁寧に解説していきます。

●C++とコンテナの基本

C++におけるコンテナとは、データの集合を扱うためのクラスのことを指します。

これらのコンテナは標準テンプレートライブラリ(STL)の一部として提供され、さまざまなタイプのデータを効率的に管理することが可能です。

C++のコンテナには、配列やリスト、マップなど、多様なデータ構造が含まれており、プログラムのニーズに応じて最適なコンテナを選択することが重要です。

○C++のコンテナとは

C++のコンテナは、データを格納し、アクセスするための様々な方法を提供します。

これには、シーケンスコンテナ(例:vector、list)、連想コンテナ(例:map、set)、および適応コンテナ(例:stack、queue)が含まれます。

それぞれのコンテナは特有の特性と用途があり、プログラムの要件に応じて選択することが可能です。

○コンテナの種類と特徴

コンテナの種類にはベクター(vector)、リスト(list)、マップ(map)などがあります。

ベクターは動的配列として機能し、要素の追加や削除が容易でランダムアクセスが可能です。

リストは二重連結リストとして機能し、要素の挿入や削除が容易でランダムアクセスは不可能です。

マップはキーと値のペアを格納し、キーに基づいて高速にデータにアクセスできます。

○コンテナの選び方

コンテナを選ぶ際には、データの特性とパフォーマンスを考慮することが重要です。

データがどのように使用されるか、例えば頻繁に要素が追加または削除されるか、データへのアクセスがランダムに行われるかなどを考慮し、データの特性に応じてコンテナを選択します。また、異なるコンテナは異なるパフォーマンス特性を持ちます。

例えば、ベクターはランダムアクセスに優れていますが、要素の挿入や削除には効率的ではありません。

●C++の基本コンテナの使い方

C++のコンテナは、データを効率的に扱うための重要なツールです。

C++には様々な種類のコンテナがあり、それぞれ異なる用途や性能特性を持っています。

基本的なコンテナには、ベクトル(vector)、リスト(list)、マップ(map)などがあります。

これらのコンテナは、データの保存、検索、ソートなどの操作を簡単かつ効率的に行うことができます。

ここでは、これらの基本的なコンテナの使い方を詳しく説明し、サンプルコードを通じて実際の操作方法を見ていきます。

○サンプルコード1:vectorの基本操作

ベクトル(vector)は、動的配列としての機能を持つコンテナです。

ベクトルは、要素の追加や削除が容易で、ランダムアクセスが可能です。

ここでは、ベクトルの基本的な操作方法を表すサンプルコードを紹介します。

#include <iostream>
#include <vector>

int main() {
    // ベクトルの初期化
    std::vector<int> v = {1, 2, 3, 4, 5};

    // 要素の追加
    v.push_back(6);

    // 要素のアクセスと出力
    for (int i = 0; i < v.size(); ++i) {
        std::cout << v[i] << " ";
    }
    std::cout << std::endl;

    return 0;
}

このコードは、整数型のベクトルを初期化し、その後に新しい要素を追加するものです。

forループを使用して、ベクトルの各要素にアクセスし、それらを出力しています。

この例では、ベクトルに1から6までの整数が保存され、それらが順番に出力されます。

○サンプルコード2:listの基本操作

リスト(list)は、双方向連結リストとして機能するコンテナです。

リストは、要素の追加や削除が容易ですが、ランダムアクセスは効率的ではありません。

ここでは、リストの基本的な操作方法を表すサンプルコードを紹介します。

#include <iostream>
#include <list>

int main() {
    // リストの初期化
    std::list<int> l = {1, 2, 3, 4, 5};

    // 要素の追加
    l.push_back(6);
    l.push_front(0);

    // 要素の出力
    for (auto it = l.begin(); it != l.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    return 0;
}

このコードは、整数型のリストを初期化し、先頭と末尾に新しい要素を追加するものです。

イテレータを使用してリストの各要素にアクセスし、それらを出力しています。

この例では、リストに0から6までの整数が保存され、それらが順番に出力されます。

○サンプルコード3:mapの基本操作

マップ(map)は、キーと値のペアを保存する連想コンテナです。

マップは、キーによる高速な検索が可能です。

ここでは、マップの基本的な操作方法を表すサンプルコードを紹介します。

#include <iostream>
#include <map>

int main() {
    // マップの初期化
    std::map<std::string, int> m = {{"one", 1}, {"two", 2}, {"three", 3}};

    // 要素の追加
    m["four"] = 4;

    // 要素のアクセスと出力
    for (const auto& pair : m) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

このコードは、文字列型のキーと整数型の値を持つマップを初期化し、新しい要素を追加するものです。

範囲ベースのforループを使用して、マップの各要素(キーと値のペア)にアクセスし、それらを出力しています。

この例では、マップに「one」から「four」までのキーと、それに対応する1から4までの値が保存され、それらが順番に出力されます。

●コンテナの応用例

C++でのプログラミングにおいて、コンテナは非常に重要な役割を果たします。

コンテナを使うことで、データの保管、アクセス、操作が容易になります。

応用例としては、データベースの管理、ゲーム開発におけるキャラクター情報の格納、ウェブアプリケーションでのセッション管理などが挙げられます。

これらの例では、異なる種類のデータを効率的に扱うために、様々な種類のコンテナが用いられます。

例えば、データベース管理では、多くの異なるデータ型を格納し、高速な検索が求められるため、mapやsetのようなコンテナが利用されます。

ゲーム開発では、キャラクターやアイテムのリストを管理するために、vectorやlistが用いられることが多いです。

ウェブアプリケーションでは、ユーザーのセッション情報を保存するために、連想配列に相当するmapを使用することがあります。

これらの応用例は、C++のコンテナが柔軟かつ強力なツールであることを表しています。

異なる用途に応じて適切なコンテナを選択することが、効率的なプログラミングへの鍵となります。

○サンプルコード4:vectorを使ったデータのソート

C++においてvectorは非常によく使用されるコンテナの一つです。

vectorを使ってデータをソートする一例を紹介します。

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> data = {4, 1, 3, 5, 2};
    std::sort(data.begin(), data.end());

    for(int i : data) {
        std::cout << i << " ";
    }
    return 0;
}

このコードは、vectorを使用して整数のリストを昇順にソートするものです。

最初に#include <algorithm>でalgorithmヘッダをインクルードし、std::sort関数を使ってvectorの要素をソートしています。

ソート後、forループを使用してソートされた要素を出力します。

このコードを実行すると、1 2 3 4 5という出力結果が得られます。

これは、vector内のデータが昇順にソートされたことを示しています。

○サンプルコード5:mapを使ったデータ管理

mapはキーと値のペアを保存する連想コンテナで、データベースのような構造を簡単に実装することができます。

#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> scoreMap;
    scoreMap["Alice"] = 90;
    scoreMap["Bob"] = 85;
    scoreMap["Charlie"] = 95;

    for(auto const& pair : scoreMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    return 0;
}

このコードでは、mapを使って学生の名前と点数を管理しています。

mapにはキーとして学生の名前、値として点数を保存しています。

forループを使ってmap内の全てのキーと値のペアを出力しています。

実行すると、各学生の名前とそれに対応する点数が表示されます。

このコードは、mapがキーに基づいたデータの管理に非常に有効であることを示しています。

○サンプルコード6:カスタムコンテナの作成

C++では、標準ライブラリのコンテナに加えて、独自のカスタムコンテナを作成することも可能です。

独自のコンテナを作ることで、特定の用途に特化したデータ構造を実装することができます。

#include <iostream>
#include <vector>

template <typename T>
class CustomContainer {
    std::vector<T> data;

public:
    void add(T value) {
        data.push_back(value);
    }

    T get(int index) {
        return data[index];
    }

    int size() {
        return data.size();
    }
};

int main() {
    CustomContainer<int> container;
    container.add(10);
    container.add(20);
    container.add(30);

    for (int i = 0; i < container.size(); ++i) {
        std::cout << container.get(i) << " ";
    }
    return 0;
}

このコードでは、独自のカスタムコンテナCustomContainerをテンプレートクラスとして定義しています。

内部にはstd::vectorを使用してデータを保持し、データの追加、取得、サイズ取得のためのメソッドを提供しています。

●コンテナのパフォーマンスと最適化

C++のコンテナのパフォーマンスと最適化は、効率的なプログラミングにとって非常に重要です。

コンテナの性能に影響を与える主な要因には、コンテナの種類、データの量、データへのアクセス方法などがあります。

例えば、vectorはランダムアクセスが高速ですが、要素の挿入や削除には適していません。

一方、listは要素の挿入や削除に優れていますが、ランダムアクセスには効率的ではありません。

このため、アプリケーションの要件に応じて、最適なコンテナの選択が重要です。

○コンテナのパフォーマンスに影響する要因

コンテナのパフォーマンスに影響を与える要因として、下記の点が挙げられます。

コンテナの種類は、データの追加、削除、検索などの操作の効率に大きく関わります。

保持するデータの量が多いほど、特に要素の追加や削除のパフォーマンスに影響が出ることがあります。

また、データへのアクセスパターンも重要で、例えば頻繁にランダムアクセスする場合は、ランダムアクセスが高速なvectorが適しています。

○効率的なコンテナの選び方と使用法

効率的なコンテナの選び方と使用法については、アプリケーションの要件を理解することが最も重要です。

アプリケーションがどのようにデータを使用するかに基づいて、最適なコンテナを選択する必要があります。

また、異なるコンテナでプロトタイプを作成し、パフォーマンス試験を行うことで、アプリケーションに最適なコンテナを決定することができます。

コンテナが消費するメモリ量も重要な要素で、メモリ使用量が多いと、パフォーマンスに悪影響を与える可能性があります。

効率的なコンテナの選択と使用は、パフォーマンスとメモリ効率のバランスを取ることで、最適なプログラムを実現するための鍵となります。

●コンテナのカスタマイズと拡張

C++でのコンテナのカスタマイズと拡張は、プログラムの柔軟性と効率を高める重要なステップです。

コンテナのカスタマイズには、標準コンテナの機能を拡張する方法や、新しいコンテナ型を作成する方法が含まれます。

ここでは、C++のコンテナをカスタマイズし、拡張するための基本的な手順と考え方を解説します。

○カスタムコンテナの作り方

カスタムコンテナを作成する際には、基本的なコンテナインターフェースの設計を理解することが重要です。

C++の標準コンテナは、イテレータ、アクセスメソッド(例えば、begin(), end())、要素の追加や削除のメソッドなど、一定のインターフェースを提供しています。

カスタムコンテナを作成する際には、これらのインターフェースを適切に実装することが求められます。

例えば、特定のソート順を保持するカスタムコンテナを作成する場合、要素の追加時にソート順を維持するロジックを実装する必要があります。

このようなカスタムコンテナは、内部的には標準のstd::vectorstd::listを使用し、これらのコンテナに要素を追加する前にソート順を計算するラッパークラスとして実装することができます。

#include <vector>
#include <algorithm>

template <typename T>
class SortedVector {
private:
    std::vector<T> data;

public:
    void add(const T& value) {
        data.push_back(value);
        std::sort(data.begin(), data.end());
    }

    const T& get(int index) const {
        return data[index];
    }

    size_t size() const {
        return data.size();
    }
};

この例では、SortedVectorクラスはstd::vectorを使用してデータを保持し、addメソッドで新しい要素を追加するたびに、全体をソートしています。

このようなカスタムコンテナは、特定の要件に合わせて動作を変更するための有効な手段です。

○既存コンテナの拡張方法

既存のコンテナを拡張する方法には、コンテナに新しい機能を追加するか、既存の機能をオーバーライドすることが含まれます。

例えば、std::vectorにログ機能を追加する場合、ベクタークラスを継承し、要素の追加や削除のたびにログを取る機能を追加することができます。

#include <vector>
#include <iostream>

template <typename T>
class LoggingVector : public std::vector<T> {
public:
    void push_back(const T& value) {
        std::cout << "Adding element: " << value << std::endl;
        std::vector<T>::push_back(value);
    }
};

この例では、LoggingVectorクラスはstd::vectorを継承しており、push_backメソッドをオーバーライドして要素が追加されるたびにメッセージをログに出力しています。

この方法により、既存のコンテナの基本機能を維持しつつ、必要な機能を拡張することが可能です。

●注意点と対処法

C++プログラミングにおけるコンテナの利用は非常に便利ですが、いくつかの注意点があります。特に、メモリ管理や例外処理に関しては慎重な対応が求められます。

ここでは、それらの注意点とその対処法について解説します。

○コンテナのメモリ管理

C++のコンテナを使用する際の最も重要な点の一つがメモリ管理です。

特に、大きなデータ構造を扱う場合や、リソースが限られている環境では、効率的なメモリ管理が必須となります。

例えば、std::vectorは動的配列を提供しますが、これを使用する際にはメモリの確保と解放に注意が必要です。

std::vectorは必要に応じて自動的にサイズを拡張しますが、不要になったメモリは自動的には解放されません。

そのため、不要になったstd::vectorのメモリを解放するには、clear()メソッドとshrink_to_fit()メソッドを適切に使用することが推奨されます。

#include <iostream>
#include <vector>

int main() {
    // ベクターの初期化
    std::vector<int> vec;

    // データの追加
    for (int i = 0; i < 100; ++i) {
        vec.push_back(i);
    }

    // ベクターのクリア
    vec.clear();

    // メモリの解放
    vec.shrink_to_fit();

    return 0;
}

このコードは、std::vectorに100個の整数を追加した後、clear()メソッドで内容をクリアし、shrink_to_fit()メソッドでメモリを解放しています。

このように適切なメモリ管理を行うことで、メモリリークを防ぐことができます。

○例外処理とエラーハンドリング

C++では、例外処理を適切に行うことで、予期しないエラーからプログラムを保護することができます。

コンテナの操作中に例外が発生した場合、これをキャッチして適切に処理することが重要です。

例えば、std::vectorat()メソッドは、指定されたインデックスが範囲外の場合にstd::out_of_range例外を投げます。

この例外をキャッチして適切に処理することで、プログラムのクラッシュを防ぐことができます。

#include <iostream>
#include <vector>
#include <stdexcept>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    try {
        // 範囲外のアクセスを試みる
        int value = vec.at(10);
    } catch (const std::out_of_range& e) {
        std::cerr << "エラー: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、範囲外のインデックスにアクセスする際にstd::out_of_range例外が発生し、これをキャッチしてエラーメッセージを出力しています。

例外処理を適切に行うことで、プログラムの安定性と信頼性を高めることができます。

まとめ

この記事では、C++におけるコンテナの基本から応用、カスタマイズ、パフォーマンス最適化に至るまでを詳しく解説しました。

コンテナの種類の理解、適切な選択、効率的な使用方法は、C++プログラミングの効率と効果を高めるために不可欠です。

また、メモリ管理と例外処理の重要性も強調しました。

これらの知識を活用して、安定した高性能なC++アプリケーションを開発することができます。