C++Builder好きの秘密基地 このページをアンテナに追加 RSSフィード

2012-12-09

UnicodeからShift-JISへの変換、どうする?

なし崩し的にDelphi Advent Calendar 2012に記事を書く羽目になった件について。(ぉぃ
まぁ、実業務でのちょっとした覚え書きな件もあるので、いい機会だし久しぶりに更新してみる。

ここ数年でプログラミング環境はUnicodeを意識せざるを得なくなった。DBエンコーディングUTF-8であることなんて良くあること。コードを書く側としてはエンコーディング変換とかはフレームワークの類いがよろしくやってくれるから余り気にする必要は無いはず。
それに、C++11でUnicodeリテラルが導入されたから、こんな感じで、いろいろアレなことが出来る。

#include <vcl.h>
#include <stdio.h>
#pragma hdrstop

#include <tchar.h>

#pragma argsused
int _tmain(int argc, _TCHAR* argv[])
{
    UnicodeString str(U"\u264aジェミニ\u264aの黄金聖闘士ご来席!\u264aジェミニ\u264aの黄金聖闘士ご来席!");
    ShowMessage(str);
    return 0;
}

こいつを実行するとこんな感じで、しっかりと星座記号が出力される。
f:id:A7M:20121208180432p:image

アプリケーションの内部はUnicodeだけど、それを加工してCSVに吐き出す場合、お客さんはUTF-8UTF-16なんてことは、わけわかめな世界であることが多々あることなので、要件定義として「CSVファイルのエンコーディングはShiftーJISとすること」ってのが追加されているはず。

VCLUnicodeからShiftーJISに変換する場合、素直にUnicodeStringからAnsiStringにキャストするのが定番と言えば定番。
以下のコードを追加して、Shift-JISに無い文字を出力するとどうなるか?

    AnsiString sjStr(str);
    printf("%s",  sjStr.c_str());

こんな感じで、コンソールに「?」マークとして出力される。
f:id:A7M:20121208182228p:image
結果をodでダンプすると、こんな感じ。
f:id:A7M:20121208182839p:image
0x3fなので、文字としてはShift-JISに存在しない文字は「?」に変換される。

でも、お客さんは「?」じゃ気に入らない。他の文字にしてくれとなるとどうするか?例えば、「_」(アンダースコア)にしてくれとか。
「s/?/_/g」じゃ駄目だよね?元々あった「?」までも置換されてしまう。

そんな場合は仕方ないので、WideCharToMultiByte APIを直呼びをする。

int _tmain(int argc, _TCHAR* argv[])
{
    UnicodeString str(U"\u264aジェミニ\u264aの黄金聖闘士ご来席!\u264aジェミニ\u264aの黄金聖闘士ご来席!");
    ShowMessage(str);

    // 変換出来無かった場合のデフォルトの文字(スペースに変換)
    const char* DefStr = " ";

    // バッファのサイズを取得
    int BufferSize = ::WideCharToMultiByte(932, 0, str.c_str(), -1, NULL, 0, DefStr, NULL);
    std::unique_ptr<char[]> szBuffer(new char[BufferSize]);

    // 文字列の変換
    int ret = ::WideCharToMultiByte(932, WC_NO_BEST_FIT_CHARS, str.c_str(), -1, szBuffer.get(), BufferSize, DefStr, NULL);

    printf("%s",  szBuffer.get());
}

でも、こいつには罠があって、変換できなかった文字に下駄文字(「〓」)のような全角文字は指定できない模様。その場合はどうしよう…。1文字単位でWideCharToMultiByteを呼び出してlpUsedDefaultCharの値をチェックするしか無いかも。

追記:
Delphiのコードが無い。ん〜!? なんのことかな フフフ…

2012-06-10

clang 3.1をWindowsで使ってみた

次期C++BuilderのC++コンパイラがLLVM/clangになる*1というので、早速いじってみた。

まずはインストール。現時点でWindows上でclangを動かすにはmingwが必要とのことで、まずはmingwインストール

  1. http://sourceforge.net/projects/mingw/files/Installer/mingw-get/catalogue/からDownload mingw-get-inst-20120426.exeをクリック。
  2. ダウンロードしたインストーラーを実行してmingwインストール

続いて、LLVMインストールLLVMのダウンロードページからWindows用のバイナリダウンロード
ダウンロードしたアーカイブ解凍して、mingwインストール先に上書き。
以上で、Windows上でclangがインストールされる。

コンパイルは以下の感じ。ネタはC++11でサポートされたラムダ式を使ってFizzBuzz問題を解いてみる。

#include <tchar.h>
#include <stdlib.h>
#include <algorithm>
#include <iostream>
#include <vector>
#include <iterator>

class CFizzBazz {

private:
  class Incl {
  public:
    Incl() : m_cnt(1) {
    }
    int operator() () {
      return m_cnt++;
    }
  private:
    int m_cnt;
  };

public:
  CFizzBazz(int max) {
    std::vector<int> v;
    std::generate_n(std::back_inserter(v), max, Incl());
    std::for_each(v.begin(), v.end(), [](int num) {
      if (num % 3 == 0 && num % 5 == 0) {
        std::cout << "FizzBazz";
      } else if (num % 3 == 0) {
        std::cout << "Fizz";
      } else if (num % 5 == 0) {
        std::cout << "Bazz";
      } else {
        std::cout << num;
      }
      std::cout << std::endl;
    });
  }
};

int _tmain(int argc, _TCHAR* argv[])
{
  CFizzBazz fizzBazz(_ttoi(argv[1]));
  return 0;
}

スタートメニューから[MinGW|MinGW Shell]を選択してコマンドラインシェルを起動。
コマンドラインから以下を入力。

$ clang -std=c++11 fizzbuzz.cpp -o fizzbuzz.exe -lstdc++

clangの最大の特徴として、エラーメッセージが非常に見やすいってのがある。C++の欠点としてテンプレートとか使うとエラーメッセージわけわかめになるのがあるけど、それが解消されている。C++11の準拠度もかなり高い感じだし、古いコンパイラを捨てて、clangに移行するのはいい決断ではないかと。

*1:もしかして、DelphiのバックエンドもLLVMになるとか?

2011-07-01

スマートポインタの使い方 その3:メモリ以外のリソースをスマートポインタで管理する

デリーターとは

スマートポインタが管理できるのは、メモリ領域だけではなく「構築」と「破棄」がペアになっている任意のリソースも管理できる。
例えば、Cのfopen関数でオープンしたファイルをfclose関数でクローズしたり、あるいは、Windows APIのGDIオブジェクトをDeleteObject関数で破棄したりとか。
その仕組みを実現するのが「デリーター」で、厳密にはスマートポインタのデフォルトのデリーターが"operator delete"となっている。

#pragma hdrstop
#include <windows.h>
#include <boost/tr1/memory.hpp>
// スマートポインタがGDIオブジェクトを破棄するデリーター
struct GDIDeleter
{
  void operator()(HANDLE handle)
  {
    ::DeleteObject(handle);
  }
};
void foo()
{
  std::tr1::shared_ptr<void>  Pen(::CreatePen(PS_SOLID, 20, RGB(255,255,0)), GDIDeleter());
  ::SelectObject(hDC, Pen.get()); // ハンドルはgetメソッドで取得
}

shared_ptrの場合は、コンストラクタにデリーターのインスタンスを渡す。デリーターは関数オブジェクトなのでoperator()でリソースの破棄を行う処理を記述する。
unique_prtの場合は、型宣言のテンプレートにデリーターの型を記述する。

void foo()
{
  std::unique_ptr<void, GDIDeleter>  Pen(::CreatePen(PS_SOLID, 20, RGB(255,255,0)));
  ::SelectObject(hDC, Pen.get()); // ハンドルはgetメソッドで取得
}

C++Builder(BCC32)でのちょっとした問題とその回避

C++Builderではvoidへのポインタをstd::unique_ptrで管理出来ない*1ので適当な型へのポインタとしてキャストするか、以下の例のようにラッパークラスを作成して誤魔化す。

//---------------------------------------------------------------------------
#include <memory>
#include <boost/shared_ptr.hpp>
#include <tchar.h>
#include <iostream>
//---------------------------------------------------------------------------
typedef int  MAPHANDLE;
MAPHANDLE LoadMap(const char* MapFilePath)
{
  MAPHANDLE MapID = 100;   // あくまでもダミー
  std::cout << "Loaded Map ID = " << MapID << std::endl;
  return MapID;
}
//---------------------------------------------------------------------------
void ReleaseMap(MAPHANDLE MapID)
{
  std::cout << "Release Map ID = " << MapID << std::endl;
}
//---------------------------------------------------------------------------
void ProcessMap(MAPHANDLE MapID, int Code)
{
  std::cout << "Process Map ID = " << MapID << std::endl;
}
//---------------------------------------------------------------------------
class CMapHandle
{
private:
//typedef void Ty_; // C++Builderだと、void*はstd::unique_ptrで管理できない。orz
  typedef int Ty_;
public:
  CMapHandle(MAPHANDLE hMap) :
    instance((Ty_ *)hMap)
  {
  }
  operator MAPHANDLE() const
  {
    return (MAPHANDLE)instance.get();
  }

private:
  class Deleter
  {
  public:
    void operator()(Ty_* ptr)
    {
      ReleaseMap((MAPHANDLE)ptr);
    }
  };
  std::unique_ptr<Ty_, Deleter> instance;
};
//---------------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[])
{
  // 地図ファイルを読み込む
  CMapHandle hMap(LoadMap("D:\\Maps\\test1.shp"));
  // 何らかの処理
  ProcessMap(hMap, 1);

  // ReleaseMap(hMap); // 後始末はデリーターが行うので不要
  return 0;
}

このコードはハンドルの型がポインタではなく整数型である例。「構築」と「破棄」がペアにさえなっていればスマートポインタとして管理できる。

*1コンパイルエラー"E2466 void & は有効な型ではない"が発生。g++やVC++だとOKなので実装の問題かな…。バグっぽいからQC送りしないと。

2011-05-29

スマートポインタの使い方 その2:shared_ptr

shared_ptrを使ってみる

unique_ptrが変数の寿命が尽きた段階でメモリ領域を開放するのに対し、shared_ptrは参照カウンタを持ち参照カウンタがゼロになるとメモリ領域を開放する。

#include <tchar.h>
#ifdef __BORLANDC__
#include <boost/tr1/memory.hpp>
namespace std
{
  using namespace tr1; // 他のコンパイラと同様にstd::tr1をstdとする
}
#else
#include <memory>
#endif
#include <iostream>

// 円を表す図形
class CPrimitiveCircle
{
public:
  CPrimitiveCircle() :
    x(0), y(0), r(0)
  {
  }
  CPrimitiveCircle(int xx, int yy, int rr) :
    x(xx), y(yy), r(rr)
  {
  }
  int x, y, r;
};

int _tmain(int argc, _TCHAR* argv[])
{
  std::shared_ptr<CPrimitiveCircle> c1(new CPrimitiveCircle(10, 20, 100));

  // 振る舞いはあくまでもポインタ
  std::cout << "count = " << c1.use_count() << " r = " << c1->r << std::endl;

  std::shared_ptr<CPrimitiveCircle> c2 = c1; // 参照回数が増える
  std::cout << "count = " << c1.use_count() << " r = " << c1->r << std::endl;

  std::shared_ptr<CPrimitiveCircle> c3 = c1; // 参照回数が増える
  std::cout << "count = " << c3.use_count() << " r = " << c3->r << std::endl; 

  return 0;
}

あくまでも「ポインタ」として振る舞うので、メンバのアクセスはアロー演算子(operator->)で行う。use_countメソッドはshared_ptrが管理しているメモリ領域の参照数を取得する。
比較も同様。operator==とoperator!=で管理しているメモリ領域が「同一」か否かもチェックできる。

生ポインタの取得と再初期化

生ポインタの取得はunique_ptrと同様getメソッドでshared_ptrが管理している領域の生ポインタを取得出来る。当然、このメモリ領域はdeleteしてはならない。同様に、resetメソッドで別のポインタで再初期化できる。unique_ptrとの違いは参照回数が減るだけで、別の同一のメモリ領域を参照しているshared_ptrそのものには影響が無い。

int _tmain(int argc, _TCHAR* argv[])
{
  std::shared_ptr<CPrimitiveCircle> c1(new CPrimitiveCircle(10, 20, 100));

  // 振る舞いはあくまでもポインタ
  std::cout << "count = " << c1.use_count() << " Addr = " << c1.get() << std::endl;

  std::shared_ptr<CPrimitiveCircle> c2 = c1; // 参照回数が増える
  std::cout << "count = " << c1.use_count() << " Addr = " << c1.get() << std::endl;

  std::shared_ptr<CPrimitiveCircle> c3 = c1; // 参照回数が増える
  std::cout << "count = " << c3.use_count() << " Addr = " << c3.get() << std::endl; // ポインタは同値

  c1.reset(new CPrimitiveCircle(0, 0, 20));  // 別のポインタで初期化
  CPrimitiveCircle* pCircle = c1.get();
  std::cout << "count = " << c1.use_count() << " Addr = " << c1.get() << std::endl;
  std::cout << "count = " << c3.use_count() << " Addr = " << c3.get() << std::endl; // 再初期化した分参照回数が減る

  return 0;
}

以下は実行結果。

count = 1 Addr = 505400
count = 2 Addr = 505400
count = 3 Addr = 505400
count = 1 Addr = 505470
count = 2 Addr = 505400  ← resetしたので、参照回数が減っている

operator boolでshaed_ptrが有効か否かのチェック、swapメソッドでポインタの交換が出来るのもunique_ptrと同様。

shared_ptrのちょっとした問題

shared_ptrは参照数で管理しているので、データ構造によっては直接的、間接的にshared_ptr同士が参照し合う現象(相互参照)が発生してメモリ領域が開放されない場合がある。以下の例は、多数の線分の接続関係からポリゴンを作成するロジックから抜粋したもの。

// 直線クラス
class CPrimitiveSegment
{
public:
  CPrimitiveSegment() :
    sx(0), sy(0), ex(0), ey(0)
  {
    std::cout << "CPrimitiveSegment::CPrimitiveSegment()" << std::endl;
  }
  ~CPrimitiveSegment()
  {
    std::cout << "CPrimitiveSegment::~CPrimitiveSegment()" << std::endl;
  }

  std::shared_ptr<CPrimitiveSegment> next; // 接続先
  int sx, sy, ex, ey;
};

int _tmain(int argc, _TCHAR* argv[])
{
  // 線分を3本定義
  std::shared_ptr<CPrimitiveSegment> s1(new CPrimitiveSegment());
  std::shared_ptr<CPrimitiveSegment> s2(new CPrimitiveSegment());
  std::shared_ptr<CPrimitiveSegment> s3(new CPrimitiveSegment());

 // 三角形を作る
  s1->next = s2;
  s2->next = s3;
  s3->next = s1;

  // shared_ptrの参照数を出力
  std::cout << "count s1 = " << s1.use_count() << std::endl;
  std::cout << "count s2 = " << s2.use_count() << std::endl;
  std::cout << "count s3 = " << s3.use_count() << std::endl;

  return 0;
}

実行結果

CPrimitiveSegment::CPrimitiveSegment()
CPrimitiveSegment::CPrimitiveSegment()
CPrimitiveSegment::CPrimitiveSegment()
count s1 = 2
count s2 = 2
count s3 = 2

参照回数が2のまま、main関数が終了しデストラクタが実行されない。いわゆる「メモリリーク」が発生する。

この場合、参照関係を一方通行にして、参照される側の参照カウンタを「変化させない」weak_ptrを使用する。

class CPrimitiveSegment
{
public:
  CPrimitiveSegment() :
	sx(0), sy(0), ex(0), ey(0)
  {
    std::cout << "CPrimitiveSegment::CPrimitiveSegment()" << std::endl;
  }
  ~CPrimitiveSegment()
  {
    std::cout << "CPrimitiveSegment::~CPrimitiveSegment()" << std::endl;
  }
  // shared_ptrから参照数を変化させないweak_ptrへ
  std::weak_ptr<CPrimitiveSegment> next; // 接続先
  int sx, sy, ex, ey;
};

int _tmain(int argc, _TCHAR* argv[])
{
  // 線分を3本定義
  std::shared_ptr<CPrimitiveSegment> s1(new CPrimitiveSegment());
  std::shared_ptr<CPrimitiveSegment> s2(new CPrimitiveSegment());
  std::shared_ptr<CPrimitiveSegment> s3(new CPrimitiveSegment());

 // 三角形を作る
  s1->next = s2;
  s2->next = s3;
  s3->next = s1;

  // shared_ptrの参照数を出力
  std::cout << "count s1 = " << s1.use_count() << std::endl;
  std::cout << "count s2 = " << s2.use_count() << std::endl;
  std::cout << "count s3 = " << s3.use_count() << std::endl;

  return 0;
}

以下は実行結果。参照回数は変化せずにデストラクタが正しく実行されている。

CPrimitiveSegment::CPrimitiveSegment()
CPrimitiveSegment::CPrimitiveSegment()
CPrimitiveSegment::CPrimitiveSegment()
count s1 = 1
count s2 = 1
count s3 = 1
CPrimitiveSegment::~CPrimitiveSegment()
CPrimitiveSegment::~CPrimitiveSegment()
CPrimitiveSegment::~CPrimitiveSegment()

2011-05-20

スマートポインタの使い方 その1:unique_ptr

スマートポインタって何?

C++において、operator newでメモリ領域(ヒープ領域)を動的に確保した場合、その領域はoperator deleteでプログラマが責任を持って解放してやらなければならない。しかし、deleteを書き忘れたり、例外が発生したときの処理を怠った場合など、それが正しく行われないことはよくある。正しく解放されなかった領域はOSやプロセスが使用可能なメモリ領域を「不正占拠」し、それが積もり積もると、OSやプロセスが停止する場合がある。

#include <memory>
class Mess {};
void f(Point p1, Point p2)
{
  Rectangle* r(new Rectangle(p1, p2));
  r->rotate(45);  // 矩形を45度回転
  // ...
  if (in_a_mess) throw Mess(); // 例外を投げてみる

  // ねぇ、例外が投げられたときの後始末は?
  delete r;
}

そこで、プログラマの負担を軽減すべく、そのメモリ領域の「寿命」の管理を自動的に行う「スマートポインタ」がC++の拡張ライブラリであるBoostに実装された。その後、C++の新規格であるC++0x(C++11)でBoostの実装をベースにしたスマートポインタがC++標準ライブラリに取り込まれた。*1
スマートポインタとは文字通り"smart"(気の利いた)なポインタのことで、スマートポインタが管理しているメモリ領域が不要になったら自動的に解放してくれる。*2メモリ領域を解放するタイミングの違いによりいくつかのタイプがある。

  • std::unique_ptr (boost::scoped_ptr) : 変数の寿命が尽きるとそのメモリ領域を開放する。*3
  • std::shared_ptr (boost::shared_ptr) : 参照カウントを持ち、参照カウントがゼロになるとそのメモリ領域を開放する。
  • boost::intrusive_ptr : プログラマが参照カウントを管理する。
  • boost::scoped_array : boost::scoped_ptrの配列版
  • boost::shared_array : boost::shared_ptrの配列版

もちろん、振る舞いはあくまでも「ポインタ」なので、見た目は通常のポインタ関連のコードと何ら変わりが無い。

#include <memory>
class Mess {};
void f(Point p1, Point p2)
{
  std::unique_ptr<Rectangle> r(new Rectangle(p1, p2)); // unique_ptrはブロックの外に達するとメモリ領域を自動開放する
  r->rotate(45);  // 矩形を45度回転
  
  // ...
  if (in_a_mess) throw Mess(); // 例外を投げてみる
  // 途中で例外が投げられてもブロックの外に出たら自動的にメモリ領域の開放を行う
  // operator deleteは不要。スマートポインタが解放する。
}

std::unique_ptrを使ってみる

std::unique_ptrはブロックの外に抜けるなど変数の寿命が尽きたら、管理しているメモリ領域を自動的に開放するスマートポインタ。その名の通り"unique"(唯一)のメモリ領域しか管理しないので、複数のunique_ptrのインスタンスが同一のメモリ領域を管理することはあり得ない。元々、C++の標準ライブラリにはスマートポインタとしてauto_ptrがあったが、仕様に少々問題があった為にC++0xからは使用が非推奨となった。その替わりとしてunique_ptrが導入された。
使い方はコンストラクタの引数にoperator newで生成したポインタを渡すだけ。*4以後、そのメモリ領域はunique_ptrの管理下に置かれる。引数を与えなかった場合は、unique_ptrが管理しているメモリ領域は「無し」とみなす。使用するシチュエーションとして考えられるのは、pimplイディオムの実装部分や、Abstract Factoryパターンが生成したオブジェクトを自動的に解放したい場合など。

#include <iostream>
#include <memory>
class TestObject
{
public:
    TestObject()
    {
        std::cout << "TestObject::Constructor" << std::endl;
    }
    ~TestObject()
    {
        std::cout << "TestObject::Destructor" << std::endl;
    }
    void message()
    {
        std::cout << "TestObject::message" << std::endl;
    }
};

int main()
{
    std::unique_ptr<TestObject> pObject(new TestObject()); // newしたオブジェクトをコンストラクタに渡す
    pObject->message(); // メンバ関数の呼び出し
    return 0;
}

メンバへのアクセスはポインタと同様にアロー演算子(operator->)で行う。インスタンスへのアクセスはポインタ演算子(operator*)。当然、deleteも必要ない。ブロックの外に出ると変数の寿命が尽きるのでunique_ptrはデストラクタで自身が管理しているメモリ領域を開放する。ただし、operator=で代入することも出来ない。

void f(Point p1, Point p2)
{
  std::unique_ptr<Rectangle> rb = new Rectangle(p1, p2); // 代入は駄目。初期化は必ずコピーコンストラクタで。
}

配列として連続した複数個のメモリ領域が欲しい場合は、[]をつけて宣言する。

#include <iostream>
#include <memory>
int main()
{
  std::unique_ptr<int[]> a(new int[10]);

  for (int i = 0; i < 10; ++i) {
	a[i] = i;
  }

  for (int i = 0; i < 10; ++i) {
	std::cout << a[i] << std::endl;
  }
  return 0;
}

生ポインタの取得

unique_ptrはあくまでもポインタをラップするユーティリティクラスである。コードの見た目をポインタのように見せているだけであり、型そのものが違う。Windows APIのような標準C++ではないライブラリやAPIを呼び出す場合、unique_ptrが管理しているメモリ領域の生ポインタが必要になる場合がある。unique_ptrが管理しているメモリ領域の生ポインタを取得するにはgetメソッドを使用する。注意しなければならないのはgetメソッドはメンバ演算子(operator.)で呼び出す。

// 地図の名前を取得するAPI
int get_map_name(int MapID, char* buffer);
void process_map_name(int MapID)
{
  int length = get_map_name(MapID, NULL);  // 必要な領域サイズを取得
  std::unique_ptr<char[]> buffer(new char[length]);  // 領域を確保

  get_map_name(MapID, buffer); // エラー!!!
  get_map_name(MapID, buffer.get()); // getメソッドで生ポインタを取得する
}

取得した生ポインタはあくまでもunique_ptrの管理下にあるので、自分でdeleteしてはならない。unique_ptrがメモリ領域を管理しているか否かは、getメソッドで生ポインタを取得するか、あるいは、operator boolでチェックする。

void f() 
{
  std::unique_ptr<TestObject> obj;  // どこも管理していないunique_ptr
  if (!obj) {
    std::cout << "ptr is not enabled" << std::endl;
  }
}

unique_ptrの再初期化

Abstract Factoryパターンのようにサブクラスで振る舞いが変化するオブジェクトを動的に生成する場合など、文脈によってunique_ptrを再初期化する必要が出てくる。unique_ptrはoperator=で代入(上書き)出来ないが、resetメソッドで別のポインタで再初期化できる。このとき、unique_ptrは自身が管理しているメモリ領域を解放するので、プログラマがdeleteでメモリ領域を明示的に開放する必要は無い。

#include <iostream>
#include <memory>

class TestObject
{
public:
  TestObject(int ID) : m_ID(ID)
  {
    std::cout << "TestObject::Constructor" << "ID = " << m_ID << std::endl;
  }
  ~TestObject()
  {
    std::cout << "TestObject::Destructor" << "ID = " << m_ID << std::endl;
  }
  void message()
  {
    std::cout << "TestObject::message" << std::endl;
  }
  int m_ID;
};

int main()
{
  std::unique_ptr<TestObject> pObject(new TestObject(100)); 
  pObject->message(); // メンバ関数の呼び出し

  // 再初期化
  pObject.reset(new TestObject(200));
  return 0;
}

上記コードの実行結果

TestObject::ConstructorID = 100
TestObject::message
TestObject::ConstructorID = 200
TestObject::DestructorID = 100
TestObject::DestructorID = 200

resetの引数にNULLポインタを渡せば、明示的にunique_ptrが管理しているメモリ領域を解放することが出来る。メモリ領域を開放せずに、ただ単にunique_ptrとメモリ領域の関係を切り離す場合はreleaseメソッドを使用する。戻り値としてメモリ領域の生ポインタが返るので、その後は通常のポインタとして扱える。当然、解放が必要であれば明示的に解放しなければならない。

void f()
{
  std::unique_ptr<TestObject> pObject(new TestObject()); 
  TestObject* pp = pObject.release(); // unique_ptrとメモリ領域の関係を切り離す

  delete pp; // 明示的に解放しないとリソースリークする
}

unique_ptrと代入演算子(operator=)

C++03まで標準であったauto_ptrが「問題」とされたのは、auto_ptr同士をoperator=で代入するとメモリ領域の所有者が=の右から左へ移り、代入元のauto_ptrの管理領域はNULLとなる為である。

#include <iostream>
#include <memory>
int main()
{
  std::auto_ptr<int> a(new int(100));
  std::auto_ptr<int> b(new int(200));

  // 単なるポインタ同士の代入のつもりだが、実はポインタの所有権が移動する
  b = a;
  
  // aはどこも参照していない抜け殻なので、Access Violationが発生
  std::cout << *a << std::endl;

  return 0;
}

一見問題なさそうなコードだが、単純な代入文であるにもかかわらず副作用が発生するのはよろしくないので、unique_ptrはoperator=を隠蔽して明示的な代入を出来なくした。

#include <iostream>
#include <memory>
int main()
{
  std::unique_ptr<int> a(new int(100));
  std::unique_ptr<int> b;

  b = a; // operator=は隠蔽されているのでコンパイルエラーが発生する。
  std::cout << *a << std::endl;

  return 0;
}

unique_ptrをメンバに持つクラスにoperator=を定義する場合など、文脈によってはunique_ptr同士で「値」をやりとりしなければならない場合がある。unique_ptrは単一の領域しか管理しないので、単一性を担保するための戦略として「中身」のやりとり以外に、管理しているメモリ領域のポインタ値の交換、もしくは譲渡が考えられる。

#include <iostream>
#include <memory>
int main()
{
  std::unique_ptr<int> a(new int(100));
  std::unique_ptr<int> b(new int(200));

  std::cout << "a = " << *a << std::endl;
  std::cout << "b = " << *b << std::endl;

  *a = *b;  // unique_ptrの管理下にある領域の「中身」をやりとりする

  std::cout << "a = " << *a << std::endl;
  std::cout << "b = " << *b << std::endl;

  return 0;
}

unique_ptrが管理している領域のポインタ値を「交換」する場合はswapメソッドを使う。

#include <iostream>
#include <memory>
int main()
{
  std::unique_ptr<int> a(new int(100));
  std::unique_ptr<int> b(new int(200));

  std::cout << "a = " << *a << std::endl;
  std::cout << "b = " << *b << std::endl;
  a.swap(b);  // unique_ptrが管理している領域を交換
  std::cout << "a = " << *a << std::endl;
  std::cout << "b = " << *b << std::endl;

  return 0;
}

ポインタを「譲渡」する場合は、C++0xで導入されたmove関数を使用する。

#include <iostream>
#include <memory>
int main()
{
  std::unique_ptr<int> a(new int(100));
  std::unique_ptr<int> b;

  if (a) std::cout << "a = " << *a << std::endl;
  if (b) std::cout << "b = " << *b << std::endl;
  // std::auto_ptrだと、b = a;に相当
  b = std::move(a); 	// unique_ptrが管理しているポインタを譲渡
  if (a) std::cout << "a = " << *a << std::endl;
  if (b) std::cout << "b = " << *b << std::endl;

  return 0;
}

move関数はオブジェクトが保持しているリソースを戻り値に譲渡*5させるヘルパ関数で、結果、譲渡元には何も残らない。C++0xで導入された「ムーブセマンティクス」と「rvalue参照」を用いて実装されている。

*1:よって、名前空間はstd(std::tr1)である。

*2:厳密にはヒープ領域だけでなく、ハンドルといったメモリとは少々違う意味合いのリソースも管理できるが、これは別項で。

*3:std::unique_ptrとboost::scoped_ptrはコンストラクタ等の実装の違いはあるけど、ほぼ同一と言っていいと思う。

*4:他に解放処理を決定するdeleterがあるが、滅多に使わないので省略。

*5:厳密には、lvalueをrvalueに変換