デフォルトコンストラクタを持たないクラスのシリアル化

Boost.Serialization で,デフォルトコンストラクタを持たないクラスをシリアル化する際の注意.
普通のシリアル化ではクラスにデフォルトコンストラクタが存在しなくても問題はない.しかし,ポインタ(boost::shared_ptrboost::scoped_ptr などのスマートポインタを含む)を通したシリアル化や当該のクラスを値型とするコンテナのシリアル化では, Boost.Serialization の内部でそのクラスのデフォルトコンストラクタを呼ぼうとする.従って,デフォルトコンストラクタのないクラスでそのようなシリアル化を行おうとするとコンパイルエラーがでる.

class C
{
public:
  C( int i )
    : i_( i )
  {}

private:
  friend class boost::serialization::access;

  template< class Archive >
  void serialize( Archive &ar, unsigned version )
  {
    .....
  }

private:
  int i_;
}; // class C

C *p;
.....
an_archive & p; // C にデフォルトコンストラクタがないのでコンパイルエラー

std::vector< C > v;
.....
an_archive & v; // C にデフォルトコンストラクタがないのでコンパイルエラー

この場合,単純には C にデフォルトコンストラクタを用意すれば O.K. だけれども,本来デフォルトコンストラクタが用意されないようなクラスはそもそもデフォルト(引数無し)で意味のある状態(valid state)を構築できない場合が多い(というか,逆の言い方をするとデフォルトで意味のある状態を提供できないからデフォルトコンストラクタを提供していない).従って, こういうクラスに Boost.Serialization のため(だけ)のデフォルトコンストラクタを public で提供するのは,意味のない危険な状態を構築できる可能性をユーザに提供してしまうので非常に好ましくない.
以上のジレンマをお手軽に解決するには,単にデフォルトコンストラクタを private で提供するだけ,という方策がよろしいんじゃないでしょうか,というのが今日のお話.

class C
{
public:
  C( int i )
    : i_( i )
  {}

private:
  friend class boost::serialization::access;

  template< class Archive >
  void serialize( Archive &ar, unsigned version )
  {
    .....
  }

  C()      // Boost.Serialization のため(だけ)に用意された
    : i_() // private なデフォルトコンストラクタ
  {}

private:
  int i_;
}; // class C

C *p;
.....
an_archive & p; // O.K.

std::vector< C > v;
.....
an_archive & v; // O.K.

private だから Boost.Serialization からアクセスできないじゃんと言われそうだけれど, Boost.Serialization がデフォルトコンストラクタにアクセスする際には boost::serialization::access 経由でアクセスするため,これで大丈夫(friend に指定されているので access からデフォルトコンストラクタにアクセスできる).一方で,一般のユーザがデフォルトコンストラクタにアクセスすることはできない.そのため,このデフォルトコンストラクタでは(シリアル化の操作のみが可能なような)多少危険な状態のオブジェクトを構築してしまっても良い.
(save_construct_data, load_construct_data という2つの関数テンプレートのカスタマイズ版を用意するのが Boost.Serialization が本来期待しているやり方みたいだけれど,すごく面倒くさい.さらに大抵の場合, private なデータメンバを触る必要性が出てくるので,これを安全にやろうとするとさらに面倒なことになる.)