【C++】ポインターのスコープをチェックする

[ PR ]
ポインターの存在を確認をチェックする方法はいくつかありますが、その中でも コンパイルを通るコードを紹介します。
※1 メモリ解放について指摘を頂いたので、修正しました ※2 ポインタとスマートポインタは、暗黙にboolに変換されると指摘を頂いたので、修正しました
プロトタイプ:たたき台
以下は、ローカル変数(ポインタ)のスコープを確認する例です。
いろいろと突っ込みどころがありますが、段々修正していきます。
#include <iostream>
#include <string>
#define bold "\e[1;30m"
#define red "\e[0;31m"
#define green "\e[0;32m"
#define cyan "\e[0;36m"
#define reset "\e[0m"
using namespace std;
// Testクラス
class Test{
public:
Test()
{
cout << red << "<< Test: created >>" << reset << endl;
}
~Test(){
cout << green << "<< ~Test: deleted >>" << green << endl;
}
};
// ログを出力
void log(string s){
cout << s << endl;
}
// ポインターがNULLならNULL、それ以外ならOKと表示
// 出力: 変数名(スコープLv): [OK or NULL]
void exists(void* t,string name,string scope){
if(t){
log(name + "(" + scope + "): OK");
}else{
log(name + "(" + scope + "): NULL");
}
}
//===========================================
// テストを実行
void scope_check1(){
cout << cyan << "[Scope Check 1]" << reset << endl << endl;
// コンパイルエラーが起こらないようにする
void* a(NULL);
void* b(NULL);
void* c(NULL);
exists(a,"a","0: before"); // NULL
exists(b,"b","0: before"); // NULL
exists(c,"c","0: before"); // NULL
log("");
{
void* a(new Test);
exists(a,"a","1: before"); // OK
log("");
{
void* b(new Test);
exists(a,"a","2: before"); // OK
exists(b,"b","2: before"); // OK
log("");
{
void* c(new Test);
exists(a,"a","3"); // OK
exists(b,"b","3"); // OK
exists(c,"c","3"); // OK
log("");
}
// ローカル変数"c"はスコープを外れている
exists(a,"a","2: after"); // OK
exists(b,"b","2: after"); // OK
exists(c,"c","2: after"); // NULL
log("");
}
// ローカル変数"b,c"はスコープを外れている
exists(a,"a","1: after"); // OK
exists(b,"b","1: after"); // NULL
exists(c,"c","1: after"); // NULL
log("");
}
// ローカル変数"a,b,c"はスコープを外れている
exists(a,"a","0: after"); // NULL
exists(b,"b","0: after"); // NULL
exists(c,"c","0: after"); // NULL
}
int main(){
log("");
{
scope_check1();
}
log("");
return 0;
}
実行結果は以下の通りです。
[Scope Check 1]
a(0: before): NULL b(0: before): NULL c(0: before): NULL
<< Test: created >> a(1: before): OK
<< Test: created >> a(2: before): OK b(2: before): OK
<< Test: created >> a(3): OK b(3): OK c(3): OK
a(2: after): OK b(2: after): OK c(2: after): NULL
a(1: after): OK b(1: after): NULL c(1: after): NULL
a(0: after): NULL b(0: after): NULL c(0: after): NULL
きちんとコンパイルを通っているのがわかりますね。
ただ、 << Test: created >> とはクラスが生成されたという意味で、 クラスのコンストラクタは呼ばれていますが、 デストラクタが呼ばれていないのがわかります。
これではヒープ領域が未開放のままのため、メモリリークが発生するかもしれません。(これについては後で説明します)
スコープを外れても有効にする
前述のコードで、変数のスコープを実行時に確認することができます。
トップレベルの同名の変数をNULLにすることによってコンパイルを通していますが、トップレベル変数の値を変更するにはどうすればよいでしょうか。
一つの方法として、以下の方法を紹介します。
#include <iostream>
#include <string>
#include <memory>
#define bold "\e[1;30m"
#define red "\e[0;31m"
#define green "\e[0;32m"
#define cyan "\e[0;36m"
#define reset "\e[0m"
using namespace std;
class Test{
public:
Test()
{
cout << red << "<< Test: created >>" << reset << endl;
}
~Test(){
cout << green << "<< ~Test: deleted >>" << green << endl;
}
};
void log(string s){
cout << s << endl;
}
void exists(void* t,string name,string scope){
if(t){
log(name + "(" + scope + "): OK");
}else{
log(name + "(" + scope + "): NULL");
}
}
//===========================================
class ScopeCheck2{
public:
void* a;
void* b;
void* c;
ScopeCheck2():
a(NULL),
b(NULL),
c(NULL)
{
cout << cyan << "[Scope Check 2]" << reset << endl << endl;
exists(a,"a","0: before"); // NULL
exists(b,"b","0: before"); // NULL
exists(c,"c","0: before"); // NULL
log("");
{
void* a(new Test);
exists(a,"a","1: before"); // OK
log("");
this->a = a; // <<< COPY REFERENCE >>>
{
void* b(new Test);
exists(a,"a","2: before"); // OK
exists(b,"b","2: before"); // OK
log("");
this->b = b; // <<< COPY REFERENCE >>>
{
void* c(new Test);
exists(a,"a","3"); // OK
exists(b,"b","3"); // OK
exists(c,"c","3"); // OK
log("");
this->c = c; // <<< COPY REFERENCE >>>
}
exists(a,"a","2: after"); // OK
exists(b,"b","2: after"); // OK
exists(c,"c","2: after"); // NULL -> OK
log("");
}
exists(a,"a","1: after"); // OK
exists(b,"b","1: after"); // NULL -> OK
exists(c,"c","1: after"); // NULL -> OK
log("");
}
exists(a,"a","0: after"); // NULL -> OK
exists(b,"b","0: after"); // NULL -> OK
exists(c,"c","0: after"); // NULL -> OK
}
};
int main(){
log("");
{
ScopeCheck2 sct;
}
log("");
return 0;
}
実行結果は以下の通りです。
さっきと異なり、トップレベル変数の値が変更され、OKが増えているのが分かります。
[Scope Check 2]
a(0: before): NULL b(0: before): NULL c(0: before): NULL
<< Test: created >> a(1: before): OK
<< Test: created >> a(2: before): OK b(2: before): OK
<< Test: created >> a(3): OK b(3): OK c(3): OK
a(2: after): OK b(2: after): OK c(2: after): OK
a(1: after): OK b(1: after): OK c(1: after): OK
a(0: after): OK b(0: after): OK c(0: after): OK
しかし、やはりデストラクタは呼ばれていません。
それもそのはずで、 deleteを呼んでいないからです。
では、早速修正してみましょう。
メモリが開放されるように修正
#include <iostream>
#include <string>
#include <memory>
#define bold "\e[1;30m"
#define red "\e[0;31m"
#define green "\e[0;32m"
#define cyan "\e[0;36m"
#define reset "\e[0m"
using namespace std;
class Test{
public:
Test()
{
cout << red << "<< Test: created >>" << reset << endl;
}
~Test(){
cout << green << "<< ~Test: deleted >>" << reset << endl;
}
};
void log(string s){
cout << s << endl;
}
void exists(shared_ptr<Test> t,string name,string scope){
if(t){
log(name + "(" + scope + "): OK");
}else{
log(name + "(" + scope + "): NULL");
}
}
//===========================================
class ScopeCheck3{
public:
shared_ptr<Test> a;
shared_ptr<Test> b;
shared_ptr<Test> c;
ScopeCheck3():
a(NULL),
b(NULL),
c(NULL)
{
cout << cyan << "[Scope Check 3]" << reset << endl << endl;
exists(a,"a","0: before"); // NULL
exists(b,"b","0: before"); // NULL
exists(c,"c","0: before"); // NULL
log("");
{
shared_ptr<Test> a(new Test);
exists(a,"a","1: before"); // OK
log("");
this->a = a; // <<< COPY REFERENCE >>>
{
shared_ptr<Test> b(new Test);
exists(a,"a","2: before"); // OK
exists(b,"b","2: before"); // OK
log("");
this->b = b; // <<< COPY REFERENCE >>>
{
shared_ptr<Test> c(new Test);
exists(a,"a","3"); // OK
exists(b,"b","3"); // OK
exists(c,"c","3"); // OK
log("");
this->c = c; // <<< COPY REFERENCE >>>
}
exists(a,"a","2: after"); // OK
exists(b,"b","2: after"); // OK
exists(c,"c","2: after"); // NULL -> OK
log("");
}
exists(a,"a","1: after"); // OK
exists(b,"b","1: after"); // NULL -> OK
exists(c,"c","1: after"); // NULL -> OK
log("");
}
exists(a,"a","0: after"); // NULL -> OK
exists(b,"b","0: after"); // NULL -> OK
exists(c,"c","0: after"); // NULL -> OK
}
};
int main(){
log("");
{
ScopeCheck3 sct;
}
log("");
return 0;
}
早速実行してみましょう。結果は以下の通りです。
[Scope Check 3]
a(0: before): NULL b(0: before): NULL c(0: before): NULL
<< Test: created >> a(1: before): OK
<< Test: created >> a(2: before): OK b(2: before): OK
<< Test: created >> a(3): OK b(3): OK c(3): OK 【C++】ポインターのスコープをチェックする【修正済】 a(2: after): OK b(2: after): OK c(2: after): OK
a(1: after): OK b(1: after): OK c(1: after): OK
a(0: after): OK b(0: after): OK c(0: after): OK
<< ~Test: deleted >> << ~Test: deleted >> << ~Test: deleted >>
正しく破棄されているのがわかります。
仕組みは、 スマートポインタを使っているからです。
今回の場合は、ポインタのアドレスをコピーしており、複雑になりがちですが、こういう時こそスマートポインタを使うことで的確に削除することができます。
是非参考にして下さい。
まとめ
今回は、スコープのチェックをするために、コンパイルを通る方法を紹介しました。
複数の変数がある場合は、メモリ管理が複雑になりがちですが、スマートポインタを使って簡潔に記述する方法を紹介しました。