2014年7月19日土曜日

[C++]unique_ptrとvector

思わぬところで躓いたのでメモ



unique_ptrを知ってから大変重宝している。
ほぼコスト0なのにdeleteをいちいち書かずに済むのでまさにスマートなポインタ。
が、しかし、見えないが故にエラーが出ると厄介だ。

次のようなプログラムを書いた。
Dataクラスにunique_ptrで確保したメモリがありそこにデータを入れる。
それをvectorにいれてデータ配列を作ろうとした。よくありそうな場面だ。

#include <memory>
#include <vector>

class Data
{
public:
  std::unique_ptr<char> Buffer;
  size_t Size;

  Data(char* _buf, size_t size)
    : Buffer(new char [size])
    , Size(size)
  {
    memcpy(Buffer.get(), _buf, size);
  };
};

int main()
{
  std::vector<Data> list;

  const int MEM_SIZE = 128;
  std::unique_ptr<char> mem(new char [MEM_SIZE]);

  Data d(mem.get(), MEM_SIZE);
  list.push_back(d);
 
  return 0;
}

しかしこれをコンパイルしたところ次のエラーが表示された。(Visual Studio 2012)


error C2248: 'std::unique_ptr<_Ty>::unique_ptr' : private メンバー (クラス 'std::unique_ptr<_Ty>' で宣言されている) にアクセスできません。


ネットで調べてみると、次のページがヒントになった。
http://stackoverflow.com/questions/22788275/stdvectorstdunique-ptrint-does-not-compile

どうやらコピーコンストラクタに原因がありそうだということだが、一体どこから呼びだそうとしているのか、地道に一行ずつコメントアウトして調べていくとpush_back()が原因だとわかった。(すぐに気付きたかった)

vectorにpuch_backで要素を入れる際、要素をコピーして渡す。その時、コピーコンストラクタが働く。そして、今回のDataクラスにはunique_ptrで確保されたメンバ変数がある。unique_ptrは代入不可・コピー不可なので、デフォルトコピーコンストラクタではコピーすることができず、上記のエラーが発生したということだった。

原因が分かったところで、Dataクラスにコピーコンストラクタを実装したところコンパイルに成功した。

  Data(const Data& rhs)
    : Buffer(new char [rhs.Size])
    , Size(rhs.Size)
  {
    // Buffer = std::move(d.Buffer);
    memcpy(Buffer.get(), rhs.Buffer.get(), Size);
  }

unique_ptrを移動させる定石であるmove関数はここでは使えない。引数がconstなので変更を加えることができないためだ。
ということで、昔ながらのmemcpyを使用した。

スマートポインタは便利なだけに、ハマったときの解決策が分からなすぎて怖い。