STLの問題点

STLはソフトウェアの構成部品として非常に強力であり、STLを使いこなすことでコード量を大幅に抑えることができます。が、一方でSTLを使うことによる問題もいくつかあります。ここに挙げる問題点の多くはSTL自身というよりテンプレートにまつわる問題です。

[コンパイラからのメッセージが不可解]

STLを使って誤ったコードをコンパイルしたとき、時としてエラーメッセージが"何を言いたいのかわからない"ことがあります。
  // 例 : 誤ったコード
  #include <list>
  #include <algorithm>

  using namespace std;

  void f() {
    list<int> c;
    sort(c.begin(), c.end()); // 何ら問題ないように思えるが...
  }
これをコンパイルします。Visual C++ 5.0では: c:\msdev\vc\include\algorithm(537) : error C2784: 'template-parameter-5 __cdecl std::operator -(const class std::reverse_iterator<`template-parameter-1',`template-parameter-2',`template-parameter-3',`template-parameter-4',`template-parameter-5'> &,const class std::reverse_iterator<`template-parameter-1',`template-parameter-2',`template-parameter-3',`template-parameter-4',`template-parameter-5'> &)' : 'const class std::reverse_iterator<`template-parameter-1',`template-parameter-2',`template-parameter-3',`template-parameter-4',`template-parameter-5'> & 用のテンプレート引数を 'class std::list<int,class std::allocator<int> >::iterator' から減少できませんでした。
...(その他たくさんのエラー)...

なんのことだかさっぱりわかりませんね。このエラーの原因はアルゴリズムsortはイテレータとしてRandomAccessIteratorを要求するのですが、listのイテレータはBidirectionalIteratorに属するためsortできないからなのです。ところがコンパイラはそんなにわかりやすいメッセージを出力してはくれません。その上、エラーと報告されたのは関数テンプレートsortの実装部です。sortを呼び出した行ではありませんから、エラーメッセージから問題を引き起こしたのがどこであるのかわからないのです。

[実行時エラー]

  // アクセス違反
  int ia[N];
  int ib[N];
  copy(ia, ib+N, ib);
  //       ^^^^         ここが誤り。正しくは ia+N
この例は配列iaの内容をibにコピーするものですが、配列iaの末尾をうっかりib+Nとしています。このコードはコンパイル時には何のエラーも発生しませんが、実行時にアクセス違反を引き起こすことでしょう。

[デバッグが困難]

  #include <string>
  #include <map>
  #include <algorithm>

  using namespace std;

  void f() {
    map<string,int> c;
    c["one"] = 1;
  }
上記コードには構文上の誤りはありません。これをデバッグするため、デバッグモードでコンパイルすると...

warning C4786: 'std::map<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,int,std::less<std::basic_string<char,std::char_traits<char>,std::allocator<char> > >,std::allocator<int> >::~map<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,int,std::less<std::basic_string<char,std::char_traits<char>,std::allocator<char> > >,std::allocator<int> >' : デバッグ情報で識別子が 255 文字に切り捨てられました。
...(その他たくさんのwarning)...

コンパイラはソースコード上で用いられたtypedefやデフォルトテンプレート引数を展開します。その結果シンボルが上記のような非常に長い文字列となります。コンパイラはデバッガのためにシンボルを実行モジュールに埋め込むのですが、シンボルとして許容できる長さ(255文字)を越えてしまったため末尾を切りつめたようです。たとえ長いシンボルを切りつめることなくデバッガに受け渡すことができたとしても、デバッグしているプログラマがこんなに長いシンボルが何を意味しているのか認識できるわけがありません。実質上デバッグ不可能です。

[標準C++準拠?]

  #include <list>
  #include <vector>

  using namespace std;

  void f() {
    list<int>   l;
    vector<int> v;
    // listの全要素をvectorに追加
    v.insert(v.end(), l.begin(), l.end());
  }
何の問題もなさそうです。が、Visual C++ 5.0でコンパイルしてみると:

error C2664: 'void std::vector<int,class std::allocator<int> >::insert(int *,unsigned int,const int &)' : 2 番目の引数を 'class std::list<int,class std::allocator<int> >::iterator' から 'unsigned int' に変換できません。

これはコンパイラが標準C++の言語仕様を完全には満たしていないことが原因です。上のコードはメンバ関数テンプレート:

    template<class Inputiterator>
      void insert (iterator position,
                   InputIterator first, InputIterator last);
を呼び出していますが、コンパイラがメンバ関数テンプレートを正しくコンパイルできないのです。標準C++の仕様を完全に満足するコンパイラは現時点ではほとんど存在しないのが実状です。そのためSTL自体もコンパイラに合わせて様々な回避策が講じられています。
  // あなたの処理系はこれをコンパイルできるか?
  // (Visual C++ 5.0 ではコンパイルエラーとなった)
  template<class T>
  struct foo {
    T data;
    template<class U> void set(const U& x);
  };

  template<class T>
  template<class U>
  void foo<T>::set(const U& x) {
    data = x;
  }

[実行サイズの膨張]

テンプレートを使う以上やむを得ないことではありますが、テンプレート関数/クラスは使われたテンプレート引数の型毎に個別のコードが生成されます。
  #include <set>
  using namespace std;

  int main() {
    set<int> is;
    is.insert(1);
    return 0;
  }>
これをVisual C++ 5.0でコンパイルしたときの実行モジュール(.exe)のサイズは29184byteでした。これにset<long>を追加してみます。
  #include <set>
  using namespace std;

  void f() {
    set<int> is;
    is.insert(1);
    set<long> ls; // 追加
    ls.insert(1); // 追加
    return 0;
  }
すると実行モジュールサイズは35328byteとなり、ソースコードでのたった2行が6Kbyteのサイズの増加を引き起こしています。テンプレートはひとつのコードからいくつもの定義が生成されます。プログラム中でset<int>,set<long>,set<char>を使ったとすると、set<T>の定義はひとつでも型の異なる3つの定義があるのと同じです。したがって様々な型に対してテンプレートを多用すると知らぬ間に実行コードサイズを膨張させてしまいます。

STLの実装ではinlineを多用しているのもサイズを大きくする一因となっているようですが、そのかわり実行速度はかなりのものです。

  /*
   * 標準関数qsortとSTLアルゴリズムsortの速度比較
   */
  #include <iostream>   // cout
  #include <algorithm>  // sort
  #include <cstdlib>    // qsort
  #include <windows.h>  // GetTickCount

  using namespace std;

  // qsortに与える比較関数
  int comp(const void* x, const void* y) {
    return *(const int*)x - *(const int*)y;
  }

  int main() {
    const int N = 1000000;
    static int a[N];
    static int b[N];
    for ( int i = 0; i < N; ++i )
      a[i] = b[i] = rand();

    long t;
    t = GetTickCount();
    qsort(a,N,sizeof(int),comp);
    t = GetTickCount() - t;
    cout << "qsort : " << t << "[ms]\n";

    t = GetTickCount();
    sort(a,a+N);
    t = GetTickCount() - t;
    cout << "sort :  " << t << "[ms]\n";

    return 0;
  }

実行結果: (Visual C++ 5.0-SP3, Windows NT4.0, Pentium-266MHz)
  qsort : 6849[ms]
  sort :  1412[ms]