LoginSignup
1
2

More than 5 years have passed since last update.

【C++】スコープファイナライザ

Last updated at Posted at 2018-12-08

概要

C++言語は厳格にはGCはありませんが、オブジェクトが破棄されるタイミングでオブジェクトのデストラクタを呼び出す仕組みがあります。その仕組みを利用することでリソースを開放したり後処理をしたりしますが、実際に書いてみると冗長になることが多い・・・ラムダなどを駆使して簡単に書けるプチライブラリを考えてみようと思います。

デストラクタを利用しない例×

std::mutex mx;
int main(){

    //スレッドを起動
    std::thread th([](){

        mx.lock();
        //...do something...

        mx.unlock();
    });

    mx.lock();
    //...do something...


    //スレッド待機処理
    //例外が途中で発生してもしなくても最後に行いたい処理
    mx.unlock();
    th.join();

    return 0;
}


「...do something...」のところでいろいろなことをするとします。
そして最後にはunlockとjoinを必ず行いたい!
でも「...do something...」の箇所で例外を投げられたら・・・
unlockとjoinは実行されることはありません。

try-catchを使えばいいじゃない×

std::mutex mx;
int main(){

    //スレッドを起動
    std::thread th([](){

        mx.lock();
        try{
            //...do something...


            mx.unlock();
        }
        catch(...){
            mx.unlock();
        }
    });

    mx.lock();
    //...do something...

    try{
        //...do something...


        mx.unlock();
        th.join();
    } catch(...){
        mx.unlock();
        th.join();
    }


    return 0;
}

例外が起こった時にその関数内で手に負えない場合は再throwが必要になるし冗長感が半端ない!

とりあえずデストラクタを利用する△

プチライブラリ

struct mutex_finalizer{
    std::mutex& mx;
    mutex_finalizer(std::mutex& m) :
        mx(m)
    {}
    ~mutex_finalizer(){
        mx.unlock();
    }
};

struct thread_finalizer{
    std::thread& th;
    thread_finalizer(std::thread& t) :
        th(t)
    {}
    ~thread_finalizer(){
        th.join();
    }
};

このようなプチライブラリを作って

利用例

std::mutex mx;
int main(){

    //スレッドを起動
    std::thread th([](){

        mx.lock();
        mutex_finalizer mx_scope(mx);

        //...do something...

    });
        mx.lock();
    mutex_finalizer mx_scope(mx);
    thread_finalizer th_scope(th);
    //...do something...



    return 0;
}

こうすると「...do something...」のところで例外が起きたとしても
最後にunlockとjoinが呼ばれますがこのプチライブラリではunlockとjoinに限られます。(ていうかstd::unique_lockを使えというツッコミみは置いといて)

ラムダとかプリプロセッサを使って改良する

プチライブラリ

template<typename FnT>
class scope_finalizer{
private:
    FnT  fn;
    bool enabled=false;
public:

    //FnT終了処理をするはラムダや関数(void (+)(void))
    scope_finalizer(FnT&& f) :
        fn(std::move(f))
    {}

    //デストラクタで終了処理を呼び出す
    ~scope_finalizer(){
        this->operator()();
    }

    //終了処理
    void operator()(){
        if(this->enabled){
            this->disable();
            this->fn();
        }
    }

    //終了処理有効化
    void enable(){
        this->enabled=true;
    }

    //終了処理無効化
    void disable(){
        this->enabled=false;
    }
};

//ラムダや関数を受け取ってscope_finalizerを作成する
struct lambda_capture{
    template<typename FnT>
    scope_finalizer<FnT> operator=(FnT&& f){
        return scope_finalizer<FnT>(std::move(f));
    }
};

//finalizer本体
//parameter scopename : 変数名(自由)
#define finalizer(scopename) auto scopename= lambda_capture()=[&]

利用例

std::mutex mx;
int main(){

    //スレッドを起動
    std::thread th([](){

        mx.lock();
        finalizer(scope){
            mx.unlock();
        };
        scope.enable();

        //...do something...

    });

    mx.lock();
    finalizer(scope){
        mx.unlock();
        th.join();
    };
    scope.enable();

    //...do something...


    return 0;
}


説明

finalizer(名前){
    //終了処理
};
名前.enable();

これで特定の処理だけでなく汎用的に書けるようになると思います。
finalizerマクロの「名前」を変えることで同じスコープに複数のfinalizerを定義することもできます。

finalizer(名前){
    //終了処理
};
名前.enable();

//...do something...

if(条件){
    名前.disable();
}

ある「条件」でfinalizerを実行したくない場合はdisableを呼び出します。

finalizer(名前){
    //終了処理
};
名前.enable();

//...do something...

if(条件){
    名前();
}

//...do something...

ある「条件」で途中でfinalizerを呼び出す場合はoperator()で呼び出します。

1
2
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2