STL のコンテナにデフォルトコンストラクタが無いクラスのオブジェクトを突っ込むお話

STL のコンテナの要素になるために必要な要件は

  • CopyConstructible (20.1.3/1 と Table 30) - 要するにコピーコンストラクタを持っていて,その意味がコピーとしてきちんと定義されていること
  • Assignable (23.1/4 と Table 64) - 要するに代入演算子(operator=)を持っていて,その意味がコピー代入としてきちんと定義されていること
  • (例外安全性の要件として,コピーコンストラクタと代入演算子(operator=)が強い例外安全性を保証すること,及びデストラクタが例外を送出しないこと(no-throw)を保証すること)

(ちなみに SGI STL の記述では上2つを合わせたものが Assignable コンセプトになっている)
以上です.特に注意すべきこととして,デフォルトコンストラクタが無いクラスのオブジェクトを突っ込むことができます.ただし,(自分が列挙できる限りでは)以下の状況で例外的にデフォルトコンストラクタが必要になります.ただし,これらは全て他の方法で(少なくとも意味的には)代替できます.

Sequence (std::vector とか std::deque とか std::list とか)のコンストラクタの一部

class C // デフォルトコンストラクタがないよぉ
{
public:
  C( int );
  C( C const & );
};

std::vector< C > v( 5 ); // デフォルトコンストラクタが必要なのでコンパイルエラー

これは Sequence のコンストラクタの引数のデフォルトとして,要素型のデフォルトコンストラクタが指定されていることに起因します.例えば,上の std::vector のコンストラクタ呼び出しは以下の std::vector のコンストラクタの宣言に対応します.

template< class T, class A = std::allocator< T > >
class vector
{
  .....
public:
  vector( size_type n, T const &x = T(), A const &a = A() );

  .....
};

なので,上のコンストラクタの呼び出しでは T(),つまり C のデフォルトコンストラクタを呼び出そうとするのでアウトとなります.これは他の全ての Sequence,つまり std::deque, std::list などに共通します.
このタイプのコンストラクタの第2引数を明示的に指定する,他のタイプのコンストラクタを使う,などの手段で代替可能です.

Sequence の resize

class C // デフォルトコンストラクタがないよぉ
{
public:
  C( int );
  C( C const & );
};

std::vector< C > v;
v.resize( 5 ); // デフォルトコンストラクタが必要なのでコンパイルエラー

上のコンストラクタの例と同じ理由です. std::vector::resize の宣言が以下のようになっているからです.

template< class T, class A = std::allocator< T > >
class vector
{
.....
public:
  void resize( size_type n, T const &c = T() );
.....
};

コンストラクタの場合と同様,第2引数を明示的に指定するなどの方法で代替可能です.

std::map 及び std::multimap の operator[]

std::map< int, C > m;
m[5] = C( 3 ); // デフォルトコンストラクタが必要なのでコンパイルエラー

std::map 及び std::multimap の operator の動作は,「キーに対応する要素があるときはそのキーに対応した要素の参照を返し,キーに対応する要素が無いときはいったん要素型のデフォルトコンストラクタで要素型のオブジェクトを生成して,そのオブジェクトの参照を返す」というものであるため,要素型のデフォルトコンストラクタが必要になります.ちなみにキーに対応する要素のあるなしに関わらず,常にデフォルトコンストラクタは要求されます(「キーに対応する要素のあるなし」は実行時の判断である一方,「デフォルトコンストラクタの要求」はコンパイル時の要求になるため).
ちなみに,規格では std::map::operator
で要素型のデフォルトコンストラクタが用いられることが明確に記述されています.

23.3.1.2 map element access [lib.map.access]

T& operator[](const key_type& x);
1 Returns: (**1.second.

operator[] の代替として insert や find などを用いることで回避できます.例えば上のコードと意味的に等価な代替コードは以下になります.

std::map< int, C > found = m.find( 5 );
if( found == m.end() ){
  m.insert( std::make_pair( 5, C( 3 ) ) );
}
else{
  found->second = C( 3 );
}

(単純に m.insert( std::make_pair( 5, C( 3 ) ) ) とやると, operator[] による値の挿入と意味が異なることに注意)
#ちなみに最後の事例は,某所で質問して,最終的に「やっぱり規格書を手元においておきたい」と決意するきっかけだったのでよく覚えていたり.

*1:insert(make_pair(x, T()))).first