2014-01-14

C++03とC++11の違い: コンテナーライブラリー編

Bazaarの記事の翻訳にだいぶ時間を取られたが、C++03とC++11の違いシリーズを再開する。今回は、コンテナーライブラリーの違いについて。

変更: メンバー関数sizeの複雑度(complexity)が定数(constant)になった。

C++03では、コンテナーのメンバー関数sizeの複雑度は規定されていなかった。これにより、例えばコンテナーの要素数に比例して線形にコストが上がるような実装も可能であった。たとえば、コンテナーの内部実装がリンクリストである場合、要素数を記録するためのデータメンバーを持たずに、メンバー関数sizeが呼び出された際に、イテレーターを全てたどって要素数をその場で計算するような実装も可能であった。

そのような実装を可能にしてしまうということは、移植性を考慮したコードでパフォーマンスの予測がたたないという問題がある。そこで、C++11では、sizeの複雑性は定数とならねばらなぬと規定された。したがって、規格準拠のコンテナーは、そのように実装しなければならない。

変更: コンテナーの要件の緩和

C++11では、新たな特性を持ったコンテナーを追加した。新たな特性を持ったコンテナーをサポートするにあたり、既存の全コンテナーが満たすべき要件は、あまりにも厳しすぎるため、その要件を緩和した。これにより、全コンテナーで保証された挙動を想定しているC++03のコードが、C++11で追加された新たなコンテナーには想定外となる場合がある。

C++11で緩和された要件は以下の通り:

  • メンバー関数sizeを持たないコンテナーが存在する。size() == 0を確かめたいならば、empty()が代わりに使える。
  • 構築後に空ではないコンテナーが存在する。(std::array)
  • swap()の複雑度が定数ではないコンテナーが存在する。(std::array)

変更: ユーザー定義型にdefault constructibleの要件

sequence containerとassociative containerで、一部の操作をする際に、テンプレート実引数で与えるユーザー定義型の要件として、default constructibleが加わった。

たとえば、以下のように書いた場合

// T型の要素でサイズが10のvector
std::vector<T> v(10) ; 

T型はデフォルト構築可能でなければならない。

C++03では、これがはっきりと規定されていなかったが、C++11では、規格で明記されるようになった。

変更:一部のメンバー関数のシグネチャ変更、戻り値の型がvoidからイテレーター型になった

コンテナーの一部のメンバー関数の戻り値の型は、C++03までは、void型であると規定されていた。ただし、イテレーターを返したほうが都合が良かったり、その時点で実装上判明しているイテレーターを、後から見つけるのにコストがかかったりするため、返して欲しい場合があった。そのため、一部のメンバー関数の戻り値の型が、voidからイテレーターを返すように変更された。

変更されたのは以下のメンバー関数である。

  • erase(iter) for set, multiset, map, multimap
  • erase(begin, end) for set, multiset, map, multimap
  • insert(pos, num, val) for vector, deque, list, forward_list
  • insert(pos, beg, end) for vector, deque, list, forward_list

これらのメンバー関数の戻り値の型が、voidからイテレーター型に変わった。

これにより、C++03コードのうち、メンバー関数へのポインターをとる合法なコードが、C++11では違法になる可能性がある。逆に、C++11で合法なコードがC++03では違法になるということでもある。

// 例
#include <vector>

int main()
{
    // well-formed in C++03
    // ill-formed in C++11
    void // return type
    ( std::vector<int>::* cpp03_ptr ) // name
    // parameter-list
    ( std::vector<int>::const_iterator,
      std::vector<int>::size_type n,
      std::vector<int>::value_type const & x)
    = &std::vector<int>::insert ;


    // ill-formed in C++03
    // well-formed in C++11
    std::vector<int>::iterator // return type
    ( std::vector<int>::* cpp11_ptr ) // name
    // parameter-list
    ( std::vector<int>::const_iterator,
      std::vector<int>::size_type n,
      std::vector<int>::value_type const & x)
    = &std::vector<int>::insert ;
} 

まあ、こんなコードはあまり書かれないのではないだろうか。

変更:シグネチャ変更、一部のメンバー関数の仮引数のiteratorがconst_iteratorになった

シグネチャが変更されたメンバー関数は以下の通り。

  • insert(iter, val) for vector, deque, list, set, multiset, map, multimap
  • insert(pos, beg, end) for vector, deque, list, forward_list
  • erase(iter) for set, multiset, map, multimap
  • erase(begin, end) for set, multiset, map, multimap
  • すべての list::splice
  • すべての list::merge

これも、メンバー関数へのポインターなど、具体的なシグネチャに依存しているC++03のコードが、C++11では修正が必要になる可能性がある。稀ではあると思うが。

変更:シグネチャ変更、resizeの仮引数の値の型がリファレンスになった。

C++03では、resizeは以下のように定義されていた。

void resize(size_type sz, T c = T());

C++11では、ムーブセマンティクスを追加したため、このシグネチャも変更することになった。また、関数を分割して、オーバーロードすることにした。

void resize(size_type sz);
oid resize(size_type sz, const T& c);

リサイズで増えた要素をデフォルト構築するのが1つ目。指定された値で初期化するのが2つ目。

resizeにムーブはない。なぜならば、複数の値を、ひとつのオブジェクトを使って初期化しなければならないからだ。

次回はアルゴリズム編。

1 comment:

Anonymous said...

> T型はデフォルト構築可能でなければならない。
> C++03では、これがはっきりと規定されていなかったが、C++11では、規格で明記されるようになった。

えー!? 自明すぎて考えたこともなかったw