melt日記

.NETすらまともに扱えないへたれのページ

ホーム 連絡をする 同期する ( RSS 2.0 ) Login
投稿数  111  : 記事  3  : コメント  8241  : トラックバック  41

ニュース

わんくま同盟

わんくま同盟

C# と VB.NET の質問掲示板

iKnow!


Dictation



書庫

C++ では、new をしたら必ず delete しなければいけません。

これについては前回説明しました。


この「~をしたら必ず~しなければならない」というのは、new と delete のペアだけではなく、malloc() と free()、fopen() と fclose()、HeapAlloc() と HeapFree()、CreateFileMapping() と CloseHandle()、CryptAcquireContext() と CryptReleaseContext() 等々、いろいろあります。

また、こういった、ペアで使用しなければならない関数やクラスを自分で作ることもあるでしょう。

これらも new, delete のペアと同じで、途中で例外が発生して後処理が呼ばれないという状況にならないようにする必要があります。


前回はそのための方法として、try, catch で囲む方法と、スマートポインタを使用する方法を説明しました。

当然スマートポインタを使う方法をお勧めします。

そのためにはスマートポインタがオブジェクトに対して任意の後処理を行える必要があって、時々それが出来ないスマートポインタもあったりするのですが、今はとりあえず除外します。


例えば boost::share_ptr で fopen() と fclose() を扱うのであれば、

struct Deleter
{
    void operator()(FILE* p)
    {
        if (p != NULL)
        {
            ::fclose(p);
        }
    }
};
boost::shared_ptr<FILE> file((::fopen("hoge.txt", "rb"), Deleter());

こんな感じでしょうか。

Deleter という関数オブジェクト(operator() を定義したクラス)を使用しているのがポイントで、これを変更することで任意の後処理を行うことが出来るようになります。

HeapAlloc(), HeapFree() であれば、

struct Deleter
{
    void operator()(void* p)
    {
        if (p != NULL)
        {
            ::HeapFree(::GetProcessHeap(), 0, p);
        }
    }
};
boost::shared_ptr<void> heap(::HeapAlloc(::GetProcessHeap(), 0, 256), Deleter());

こんな感じです。


boost::shared_ptr 等の汎用的な後処理が可能なスマートポインタを使うと、自作のクラスだろうが何だろうが全てスマートポインタで済ますことが可能になります。

こうすれば解放忘れや例外発生によるリークを防ぐことが出来るようになり、また、解放漏れのことを考えず、簡単にソースを読めるようになるため、堅牢性・保守性・可読性の高いコードになるのではないかと思っています。


まあ前回も言った通り、やっぱりスマートポインタを使いこなせるようになるまでが面倒なんですが、それでもスマートポインタを勉強する価値はあると思います。




おまけ:

スマートポインタの実装の多くは、ポインタの参照数を管理するための領域を new によって確保しているけれども、もしこれが失敗したらセットしようとしたポインタがリークしてしまうやん?とか思って boost::shared_ptr の実装を見てみました。

try
{
    pi_ = new sp_counted_impl_pd<P, D>( p );
}
catch(...)
{
    d(p); // delete p
    throw;
}

sp_counted_impl_pd は p の参照数を管理するためのクラスで、d は Deleter() のことです。

new の部分を try, catch で囲んで、失敗した場合はちゃんとポインタを削除してから例外を再送しているようです。

さすが Boost Library、自分が懸念を覚えるなんて100年早いと言わんばかりの実装ですw

投稿日時 : 2007年6月13日 1:10

コメント

# re: [C++]例外安全、new 以外について 2007/06/13 8:53 アキラ
早くC++0xで遊びたいですね~

Boostはおもしろいですが仕事では使えないので…

# re: [C++]例外安全、new 以外について 2007/06/13 11:14 シャノン
こんな問題思いついた。

CHoge::CHoge()
{
 m_pHuga = new Huga();
 m_pPiyo = new Piyo(); // ここで例外出たらどーなのよう
}

void * CHoge::operator new( size_t size, int foo /* なんだこの引数わ!*/ )
{
 m_pHuga = new Huga();
 m_pPiyo = new Piyo(); // ここで例外出たらどーなのよう
}

# re: [C++]例外安全、new 以外について 2007/06/13 11:17 melt
boost から必要な部分を抜き出して使いましょうw

C++0x、スマートポインタとか正規表現はいいとしても、関数オブジェクト(function, bind, placeholder)とか MPL(type_traits)とかがマニアック過ぎですw

# re: [C++]例外安全、new 以外について 2007/06/13 11:31 melt
>CHoge::CHoge()
分かってて言ってるんでしょうけど(というかコンストラクタでの例外のことを調べてるとシャノンさんがいたようなw)、コンストラクタで例外が発生した場合はデストラクタは呼ばれないので、確実にクリーンアップしましょう。

CHoge::CHoge() : m_pHuga(NULL), m_pPiyo(NULL)
{
 try
 {
  m_pHuga = new Huga();
  m_pPiyo = new Piyo(); // ここで例外出でもだいじょ~ぶ
 }
 catch (...)
 {
  delete m_pHuga;
  delete m_pPiyo;
  throw;
 }
}

そういえばこれ、スマートポインタだったらリークするような……。

>void * CHoge::operator new( size_t size, int foo /* なんだこの引数わ!*/ )
これも同じように解放すればいいような?
というかメンバは使えない気がw
とりあえず size_t 分の領域を返してください(><;)

# re: [C++]例外安全、new 以外について 2007/06/13 12:01 シャノン
> そういえばこれ、スマートポインタだったらリークするような……。

しません。
スマートポインタ自体のインスタンスは new で確保されないので、Huga の構築が完全に終わった後で Piyo で例外が出れば、Huga のデストラクタが呼ばれてから catch ブロックに飛び込みます。

> というかメンバは使えない気がw

うん、使えなかったw 適当に書いたのバレバレw
ま、(非常に行儀は悪いけど)static 領域とかグローバル領域にメモリ確保してたらどうしよう、的な問題として。

じゃあもっとマニアックな問題w

class Hoge
{
private:
 const Fuga * m_pFuga;
 const Piyo * m_pPiyo;

public:
 Hoge() :
  m_pFuga( new Fuga() ),
  m_pPiyo( new Piyo() ) // ここで例外出たらどーすんのよう
 {
 }
};

# re: [C++]例外安全、new 以外について 2007/06/13 12:06 シャノン
> とりあえず size_t 分の領域を返してください(><;)

返すコードをどこに書くか、がちと問題。

# re: [C++]例外安全、new 以外について 2007/06/13 15:21 melt
>Huga のデストラクタが呼ばれてから catch ブロックに飛び込みます。

CHoge::CHoge()
{
 m_huga = shared_ptr<Huga>(new Huga());
 m_piyo = shared_ptr<Piyo>(new Piyo()); // ここで例外が発生
}

これだと CHoge のデストラクタが呼ばれないので m_huga のデストラクタも呼ばれず、リークするのではないのではと思ったんですが、調べてみると、生成された後のメンバのデストラクタ呼び出しも保証してくれるんですね。

>じゃあもっとマニアックな問題w
function-try block を使えば解決するかなぁと思ったんですが、ポインタが NULL に初期化されていないから結局 delete 出来ないのでダメですね。

 Hoge() :
  m_pFuga(NULL),
  m_pPiyo(NULL)
 {
  try
  {
   m_pFuga = new Fuga();
   m_pPiyo = new Piyo();
  }
  catch (...)
  {
   delete m_pFuga;
   delete m_pPiyo;
   throw;
  }
 }

こんな感じで。
Fuga* const m_pFuga; とか書いてた場合は const_cast<> してくらはいw

# re: [C++]例外安全、new 以外について 2007/06/13 15:40 シャノン
> ポインタが NULL に初期化されていないから結局 delete 出来ない

そりゃそーだ。メンバごとに「初期化済みフラグ」でも持てばできるけど、やりたくないねぇ。
こんなケースでも、tunction-try-blockとスマートポインタを使えばおっけー。

ちなみに1コ前の問題。
operator new と operator delete は対で定義しなければなりません(正確には operator new[] と delete[] もね)から、

void * Hoge::operator new( size_t size )
{
 return malloc( size );
}

void Hoge::operator delete( void * p )
{
 free( p );
}

とやります。
さて、new はsize 以外にも引数を取ることができます。
配置 new が有名で、これは既存のメモリブロックにオブジェクトを配置するので例外の心配はありません。
また、new( std::nothrow ) もこれです。

で、

void * Hoge::operator new( size_t size, int i );

を定義すると、

Hoge * pHoge = new( 1 ) Hoge();

なんてのが可能になります。
これの破棄は通常通り、

delete pHoge;

で消します。もちろん、operator delete は定義しておかなければなりません。

が!

第2以降の引数を取る operator new を定義しておいて、コンストラクタで例外が発生してしまうと、void * だけを引数に取る operator delete は呼ばれないため、メモリリークが発生します。
これを防ぐためには、new に対応する delete 、つまり

void Hoge::operator delete( void *, int );

を定義しておかなければなりません。
しかし、この delete を明示的に呼ぶ方法はありません。
正常系で、

delete( 1 ) pHoge;

と書くことはできません。

そのため、
・operator new を定義したら、operator delete も必ず定義する。
・コンストラクタで例外が発生し得る場合、operator new と同じ引数を取る operator delete が呼ばれるので、これを定義する。
・メモリの解放は、単一引数と第2引数以降付きの operator delete の両方でできなければならない

となります。

# re: [C++]例外安全、new 以外について 2007/06/13 17:09 melt
なるる、そういう意味だったんですね。

>しかし、この delete を明示的に呼ぶ方法はありません。
pHoge->~Hoge();
Hoge::operator delete(pHoge, 1);

これで一応明示的に呼べてる気はしますが、こういうのはダメですか?

# re: [C++]例外安全、new 以外について 2007/06/13 17:15 シャノン
> こういうのはダメですか?

それは見落としていたけれど、new( 1 ) で作ったオブジェクトは、必ずそのように2段階削除をしなければならないってルールは漏れそうな気がします。
配列の場合は、デストラクタを呼ぶために自前でループまでしてやらなきゃなりませんしね。

# re: [C++]例外安全、new 以外について 2007/06/13 22:11 melt
そこでスマートポインタの(ry

# re: コンストラクタで例外が発生しちゃったら? 2007/09/19 13:31 とりこびと ぶろぐ。
re: コンストラクタで例外が発生しちゃったら?

# re: コンストラクタで例外が発生しちゃったら? 2007/09/19 15:43 とりこびと ぶろぐ。
re: コンストラクタで例外が発生しちゃったら?

# re: コンストラクタで例外が発生しちゃったら? 2007/09/19 15:44 とりこびと ぶろぐ。
re: コンストラクタで例外が発生しちゃったら?

# mAWXUXBqiecElpoVMZe 2022/04/19 10:04 markus
http://imrdsoacha.gov.co/silvitra-120mg-qrms

Post Feedback

タイトル
名前
Url:
コメント