2014-09-16

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

Categories: C++ ポインター
cpp.png

[ 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 >>


正しく破棄されているのがわかります。

仕組みは、 スマートポインタを使っているからです。

今回の場合は、ポインタのアドレスをコピーしており、複雑になりがちですが、こういう時こそスマートポインタを使うことで的確に削除することができます。

是非参考にして下さい。

まとめ

今回は、スコープのチェックをするために、コンパイルを通る方法を紹介しました。

複数の変数がある場合は、メモリ管理が複雑になりがちですが、スマートポインタを使って簡潔に記述する方法を紹介しました。

C++ の絵本
C++ の絵本
posted with amazlet at 14.09.16
(株)アンク
翔泳社
売り上げランキング: 101,758

コメントはTwitterアカウントにお願いします。

RECENT POSTS


[ PR ]

.