Standard Template Library

概要

STLは、その名の通りC++の比較的新しい機能であるテンプレートを最大限に生かす構成を取っており、コンテナ、イテレータ（反復子）、アルゴリズム、そして関数オブジェクトと呼ばれる各要素から成っている。
テンプレート機能を駆使することで、決まった型に依存しないプログラミングを実現し、C++におけるジェネリックプログラミングのはしりとなった。オブジェクトを格納するコンテナとそれを操作するアルゴリズム、その接点としてイテレータが存在し、コンテナとアルゴリズムが互いに完全に独立しているのも特徴の一つである。
それぞれの要素はコンセプト（概念）と呼ばれる要求仕様が厳格に規定され、STLと共に動作するコンポーネントはこの要求を満たしていなければならない。しかしそれによって、条件を満たしたクラスや関数を自作し、STLのコンポーネントと（あたかもSTLの一部であるかのように）協調動作させることが簡単にできるようになっている。STLの要素は何かcontainerといったような基底クラスから派生するのではない。コンセプトを満たしていればどのような要素でも協調動作する点では、動的な型付けシステムを持つ言語のダック・タイピングと良く似るが、STLのそれは静的（テンプレートによるコンパイル時解決）であり、よく比較される。
STLの定義（何がSTLの一部であるか）には様々な意見があり、人によりその解釈は違っていることがある。ここではC++標準ライブラリのコンテナ、イテレータ、アルゴリズム、関数オブジェクトを指すものとする。

アルゴリズム

STLで言うアルゴリズムは一般的なアルゴリズムより限定された用語であり、イテレータで指定されたコンテナの要素に対して特定の操作を行う関数である。
STLのアルゴリズムは標準で定義されているものだけでも多数あり、100以上の強力なアルゴリズムが用意されている。
std::vector<int> v1(3), v2(3);v1[0] = 2;v1[1] = 1;v1[2] = 7;std::copy(v1.begin(), v1.end(), v2.begin());
copyは要素のコピーを行うSTLアルゴリズムである。コピー元としてコンテナv1の開始イテレータと終了イテレータを取り、v2のイテレータに順に出力する。
この例ではコピー元とコピー先のコンテナは同じ型であるが、コンテナ要素が同一あるいは変換可能であればよい。例えば、vector<int>からlist<int>、set<float>からvector<double>等でもよい。

関数オブジェクト

関数オブジェクトはoperator()（関数呼び出し演算子）をオーバーロードしたクラスの一種であり、オブジェクトでありながら関数のように呼び出せるという特徴を持つ。ファンクタ (functor) とも呼ばれる。
関数オブジェクトの定義は例えば以下のようなものである。
struct square{
    template<typename T>
    T operator()(T n) const { return n * n; }};
一般に関数オブジェクトで表された関数はオブジェクトとして作成したものをアルゴリズムに値渡しするため、同じような目的で使用できる関数ポインタよりもオーバーヘッドが大きいと思われることもあるが、実際にはコンパイラの最適化でコードのインライン展開が行われ関数呼び出しが無くなる可能性が高いため、関数ポインタを使うよりも高速であることが多い。例えばC標準ライブラリのqsortよりSTLアルゴリズムのsortの方が往々にして高速であるというのはよく知られている。
STLでは関数オブジェクトの代わりに関数ポインタを使うことも可能である。
また、標準規格C++11より導入されたラムダ式を使うことで、関数オブジェクトの定義をより簡潔に記述できるようになった。

配列とポインタ

STLの長所の一つにCの配列との親和性の高さがある。具体的にはポインタがそのままRandomAccessIteratorとして使用できる。実のところイテレータの走査の方法はポインタ演算を模倣して作られている。アルゴリズムで配列を扱う場合、配列の先頭要素を指すポインタが開始イテレータ、そのポインタに要素数を加算したものが終了イテレータになる。
std::vector<int> v(N);const int N = 16;int array[N]; // [0..15]// このときarrayが配列の最初の要素へのイテレータ、// array + Nが最後の要素の次へのイテレータとして使用できる。std::copy(array, array + N, v.begin());

プログラムサイズの増大

C++のテンプレートはテンプレート引数が違うとそれぞれ全く別の型として扱われ、それぞれ別の機械語コードにコンパイルされてしまう。
以下のようなプログラムをコンパイルすると、std::vector<int>のコードが作成される。
// vectorコンテナを使う#include <vector>
 
std::vector<int> vecInt;
ここへさらに、
std::vector<double> vecDouble;
というオブジェクトを実体化してコンパイルすると、先とは別にstd::vector<double>のコードも作成され、結果として二つ分の別個のクラスのコードが実行ファイルに含まれることになる。このため最終的に生成されるコードの量が急激に増大しやすく、組み込み向けなど記憶容量に制限のある環境ではSTLに限らずテンプレート全般の使用を禁止している開発現場もある。ただのクラスで実装した場合も事情は同じである。テンプレートを使わずに、int型用にstd::vector<int>と同等のクラスを書き、double型用にstd::vector<double>と同等のクラスも書き、両方のクラスをインスタンス化すれば、同様にコードの増大が起こる。しかしながら、PCのようにメモリやストレージ容量に余裕のある環境に限れば、実行プログラムとして出力されるコード量の増大は微々たるものと言える。

未対応コンパイラ

STLはほとんど全ての要素がC++の比較的新しい機能であるテンプレートを用いて作られているので、テンプレート関連のC++標準への準拠度が低いコンパイラやテンプレート機能を持たないEmbedded C++では、一部に使用の制限があったりSTLの使用自体が不可能なことがある。
ただし2003年にはC++標準への準拠度が高められた最適化コンパイラであるVisual C++ Toolkit 2003がマイクロソフトから無償配布されるなど、多くの環境ではテンプレートの利用における問題は少なくなってきている。
なお前述のコード量増大もそうであるが、これらはC++のテンプレート機能一般の問題であってSTL固有のものではない。

難解さ

一般にSTLは柔軟で強力であると評価されているが、ライブラリを十分に生かしたプログラミングを行おうとすると、従来のC/C++プログラミングからは大きく離れた考え方や書法が必要になる。そのため、STLの使用経験を持たないプログラマには、一見して何をしているのか全くわからないソースコードになってしまうことがある。それは例えば以下のようなものである。
size_t N = 0;int *pdata = NULL;GetData(&pdata, &N); // データとデータ数を得る
 
typedef std::deque<int> Cont;Cont deq(pdata, pdata + N);FreeData(&pdata, &N); // データを解放// 典型的なSTLコードdeq.erase(
        std::partition(
                deq.begin(),
                deq.end(),
                std::bind1st(std::less<Cont::value_type>(), 20)
        ),
        deq.end());
このコードは、何かのAPIから得られたデータの配列をdequeにコピーし、そのdequeの内容のうち20より大きい要素をコンテナの先頭に移動させた上で残りを削除している。
これほど複雑な処理がこれだけのコード量で表現できるのは素晴らしいとも言えるが、一時的なtypedef・クラス内宣言型・入れ子のアルゴリズム・関数オブジェクト・関数アダプタなどを使用しており、STLの使い方を知らない場合には非常に理解が難しくなるのは事実である。
ちなみに、こういった一時オブジェクトを使うために入れ子になった関数呼び出しは関数型プログラミング言語の書式に類似しており、実際に同じ考え方でできている側面がある。