メモリリーク検出器とstd::unique_ptr
久し振りに書く記事はC++は書けたがC++11をやりたての貝柱が実際にハマったことについて書こうと思う。
もちろん今でもその疎さはまだまだ解消されていないので初心者といえる。
はてなブログも久し振りに書くのでそのリハビリにも良いかもしれない。使い勝手が色々変わっていてまだ良くわかっていない。
貝柱は仕事柄C++のIDEとしてVisual Studioを使っている。
もちろん都合上生ポインタも扱うのでメモリリークには気を使う。
Visual Studioには便利なメモリリーク検出器があり、Win32プロジェクトのDEBUGモードなら以下のコードによってメモリリークを検出できる。
#include <iostream> //======== メモリリークの検出器 =======// #ifdef _DEBUG #define _CRTDBG_MAP_ALLOC #include <crtdbg.h> #define new new(_NORMAL_BLOCK, __FILE__, __LINE__) #endif int main(void){ //..... do something .....// _CrtDumpMemoryLeaks(); return 0; }
例えば、生ポインタを扱っていて、開放し忘れを検知したい時にこのコードは威力を発揮する。
_CrtDumpMemoryLeaks();
までにnewしたのにdeleteしなかったメモリが存在する場合、コンパイラは例えば以下のようにメモリダンプを吐き出す。
{63} normal block at 0x003F6A00, 80 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
ところで、C++11で追加されたstd::unique_ptrは、宣言したスコープから外れると、自動的に確保したメモリをdeleteしてくれる便利なスマートポインタだ。
例えば以下のような使い方をする。
{ std::unique_ptr<int> p(new int(100)); } //ここでpは開放される
そこできちんとstd::unique_ptrによってメモリが開放されてるかを確認したいとする。
ハマったのは以下のように使用した時だった。
#include <iostream> #include <memory> //======== メモリリークの検出器 =======// #ifdef _DEBUG #define _CRTDBG_MAP_ALLOC #include <crtdbg.h> #define new new(_NORMAL_BLOCK, __FILE__, __LINE__) #endif int main(void){ std::unique_ptr<int> p(new int(2)); _CrtDumpMemoryLeaks(); return 0; }
この例ではメモリリークを検知してしまう。何故だろう。答えは本当に至極単純だ。
_CrtDumpMemoryLeaks()はその時点で開放されていないメモリをダンプする。
一方std::unique_ptrの開放タイミングはそのスコープが外れる時だ。すなわち、_CrtDumpMemoryLeaks()の後になる。
_CrtDumpMemoryLeaks()はスコープから外れていないstd::unique_ptrのメモリをメモリリークとみなし、ダンプしてしまうわけだ。
なので、以下のようにコードをスコープで囲まなければならない。
#include <iostream> #include <memory> //======== メモリリークの検出器 =======// #ifdef _DEBUG #define _CRTDBG_MAP_ALLOC #include <crtdbg.h> #define new new(_NORMAL_BLOCK, __FILE__, __LINE__) #endif int main(void) { { std::unique_ptr<int> p(new int(2)); } _CrtDumpMemoryLeaks(); return 0; }
初歩的なミスで熟練には笑われてしまうかもしれない。
特にカスタムデリータのテストをしたい時などに短いプログラムを書くときは、注意しておこう。