読者です 読者をやめる 読者になる 読者になる

vectorとunique_ptr その2

前回はコピーすべきところでムーブすべきではないことがわかった。
コードはshared_ptrを用いることで、すべてがうまくいくようになる。

class C
{
  public:
    shared_ptr<int> d;
    C() : d(new int(0)) {}
    C( const int n ) : d(new int(n)) {}
    // コピーコンストラクタ、オペレータ=は自動生成でok
    ~C() {}
};

bool operator<(const C& lhs, const C& rhs )
{
  return lhs.d.get() < rhs.d.get();
}

void test_code()
{
  vector<C> v;

  C a(3), b(2), c(1);
  v.push_back(a); v.push_back(b); v.push_back(c);

  sort( v.begin(), v.end() ); // OK
}

ただし、shared_ptrは生のポインターやunique_ptrを用いる場合と比べてリソースやスピードなどのコストがかかる。

ポインターをムーブさせることがだめなのであるから、コピーはコピーということに立ち返れば、unique_ptrを使うこともできる。

class C
{
  public:
    unique_ptr<int> d;
    C() : d(new int(0)) {}
    C( const int n ) : d(new int(n)) {}
    C( const C& lhs ) : d(new int(*lhs.d.get())) {}// コピーコンストラクタ
    const C& operator=( const C& lhs ) // 代入
    {
      *d.get() = *lhs.d.get();
      return *this;
    }
    ~C() {}
};

bool operator<(const C& lhs, const C& rhs )
{
  return *lhs.d.get() < *rhs.d.get();
}

void test_code()
{
  vector<C> v;
  
  C a(3), b(2), c(1);
  v.push_back(a); v.push_back(b); v.push_back(c);

  sort( v.begin(), v.end() ); // OK
}

上記コードでは、コピーでポインターをムーブさせるのではなく、ポインターの先をコピーさせるように変更している。

 

どちらがよいのだろうか?
shared_ptr版は、ポインターの先を共有するので、ポインターのコピーとしてはサイズが大きくなりかつ低速となる。しかしオブジェクトのコピーを行わない。
unique_ptr版は、ポインターの先を共有しない。このためポインターのコピーとは別にオブジェクトの生成コストがかかる。通常オブジェクトの生成はポインターのコピーよりずっと遅い。
ポインターを使いたくなるシーンを考えると、class Cの実装はshared_ptr版の方がメモリーサイズも速度も速くなる可能性が高いと思われる。また、書くべきコードも少なく見通しもよいだろう。
unique_ptr版の方がよい場合は、vectorにすべて異なるオブジェクトを格納し、かつ静的なコンテナ(変更しない読み出し専用)として用いる場合だけではないだろうか。