vectornew

vector を使っていてたまにハマるのは、 new した領域を内部に抱え込んでいるオブジェクトを vector の要素として扱う場合である。

vector の要素となるオブジェクトのクラスは、コピーコンストラクタを 用意しなければならない。これはよいのだが、 問題は、vector に要素を追加しようとしたとき、 vector 内部でコピーコンストラクタを使ったオブジェクトのコピーが 勝手に発生することである。次のようなコードを考えてみよう。

  class Foo {
  private:
      char *buffer;
  public:
      // デフォルトコンストラクタ
      Foo() : buffer( 0 ) { }

      // デストラクタ
      ~Foo() { delete[] buffer; }
      
      // バッファ確保
      void allocate( size_t size ) {
          buffer = new char[size];
      }
  };

  int main()
  {
      vector<Foo> foos;

      foos.push_back( Foo() );         // (1-0)
      foos.back().allocate( 100 );     // (1-1)
      ……
      ……
      foos.push_back( Foo() );         // (2-0)
      foos.back().allocate( 100 );     // (2-1)
      ……
      ……
  }  

このコードの意図は、いくつかの Foo オブジェクトの単なる格納場所として vector を使いたい、ということであり、 明示的にコピーコンストラクタを使うことはない。

(1-0) で、まず Foo の一時オブジェクトが作られる。 これがコピーコンストラクタによって、 vector foos に追加される。 その後、最初に作った一時オブジェクトはデストラクトされるが、 この時点では、まだ allocate を呼んでいないので buffer は NULL であり、 これを delete しても特に何も生じない。 (1-1) でメモリ領域を allocate し、そのアドレスが buffer に格納される。

さて、次が問題なのだが、(2-0) で 何個目かのオブジェクトを追加しようとしたとき、 vector 内部でオブジェクトの再配置が発生することがある。 再配置が発生すると、vector はオブジェクトを移動するのに、 コピーコンストラクタを使用するのである。当然、その後古いほうのオブジェクトは 破棄される。つまり、buffer が delete されてしまうわけである。 Foo は、コピーコンストラクタの明示的な実装を提供していないから、 新しく作られたオブジェクトの buffer は、 古いほうの buffer (すでに破棄されている) と同じ場所を指している。 という次第で、おなじみの dangling pointer の問題が発生するわけである。

つまり、オブジェクトの単なる容れ物として vector を扱うのは危険が多い、 ということである。もし、ポインタを抱えているオブジェクト自体を vector の要素として扱いたいなら、 そのポインタには参照回数カウント付きのスマートポインタを使用すべきである。 さもなければ、オブジェクト自体を vector の要素とするのではなく、 オブジェクトへのポインタを要素としたほうがよい。でも、そうすると今度は、 vecotr を破棄してもその要素 (ポインタ) の指している先のオブジェクトまでは破棄されないので、 何か明示的に破棄するコードが必要になってくる。たとえば、

  vector<Foo *> foos;
  ……
  // foos の各要素が指しているオブジェクトを破棄
  for ( vector<Foo *>::iterator it = foos.begin();
        it != foos.end();
        ++it )
  {
        delete *it;
  }  

初出: 2001年12月15日

C++ ラビリンスの目次へ


トップページへ / Last modified: 2001-12-15 23:56:54 JST
Created by OKA Toshiyuki < oka-t@fides.dti.ne.jp >