質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

1回答

9212閲覧

お約束ごとのあるコールバック関数の書き方について

drednote

総合スコア336

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2016/07/22 16:12

今、C++で書かれたアプリケーションがあり、このアプリケーションに拡張用のスクリプト言語(mrubyを想定)を実装しています。
このスクリプト言語からアプリケーションを操作したり、オブジェクトを取得する為のコールバック関数を登録するのですが、このコールバック関数の頭に
必ず入るコードがあります。
コールバック関数はstaticメンバ関数か、あるいは通常の関数でなければならない為、クラスのインスタンスがThisで渡されるという事はありませんので、
スクリプト言語管理用クラス(CmrubyManとする)のインスタンスを取得する関数(GetInstanceとする)が各コールバック関数の頭に書かれます。
この頭に書かれる部分は完全に共通な為、出来るだけ纏めたい(1行以下にしたい)訳ですが、この書き方についてです。

幾つか実装手段を考えました。


1.テンプレートクラスを使う

C++

1//テンプレートクラス 2template <class Proc> class CMRubyCallbackTemplate { 3public: 4 static mrb_value Caller(mrb_state *mrb, mrb_value self) { 5 CMrubyMan *instance ; 6 instance = CMrubyMan::GetInstance() ; 7 if(instance == NULL) { 8 // 共通エラー処理 9 return mrb_nil_value() ; 10 } 11 return Proc::DoCallback(instance, mrb, self) ; 12 } 13} 14 15//呼び出されるメソッドの定義クラス 16class CMrbMethod1Class 17{ 18public: 19 static mrb_value DoCallback(CMrubyMan *instance, mrb_state *mrb, mrb_value self) { 20 実際の処理 21 return22 } 23} ;

登録時に

C++

1mrb_define_method(mrb, UserClass, "Method1", CMrubyCallbackTemplate<CMrbMethod1Class>::Caller, MRB_ARGS_NONE()) ;

という感じで


2.昔ながらのマクロ化

C++

1#define GetInstance() CMrubyMan *instance ; \ 2 instance = CMrubyMan::GetInstance() ; \ 3 if(instance == NULL) { \ 4 return mrb_nil_value() ; \ 5 }

と定義しておき

C++

1mrb_value Method1(mrb_state *mrb, mrb_value self) { 2{ 3 GetInstance() ; 4 実際の処理~ 5 6 return7}

で登録は

C++

1mrb_define_method(mrb, UserClass, "Method1", Method1, MRB_ARGS_NONE()) ;

という感じで。


他にも恐らくstd::functionを使う方法や他にもあると思いますが、結局どう書くのが一番スマートなのか?という点についてです。
テンプレートクラスを使う方法は拡張性があると思いますが、記述が長ったらしく、しかもメソッド毎にclassを1個作るのが
とても冗長に感じます。
マクロ化の方は記述が簡単で素直な方法だと思うのですが、例えば頭だけでなくおしりにも共通処理を入れたいとなった途端に破綻します。

とりあえず現状はマクロ化でやっているのですが、皆さんならどうしますか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

こんにちは。

テンプレートクラスを使う方法は拡張性があると思いますが、記述が長ったらしく、しかもメソッド毎にclassを1個作るのがとても冗長に感じます。

このケースではその通りと私も感じます。ですので、私ならマクロを使うだろうと思います。
おしりにも共通処理を入れる場合は、RAIIパターンを使うと思います。(下記の例は、RAIIという名前には合致しないので、厳密には異なります。)

ざっと書くと下記のような感じです。(コンパイルしてませんのでエラーがあったらすいません。)

C++

1struct ScopeExit 2{ 3 ~ScopeExit() 4 { 5 // ここに終了処理を記述する 6 } 7}; 8 9#define GET_INSTANCE() \ 10 ScopeExit aScopeExit() ; \ 11 CMrubyMan* instance ; \ 12 instance = CMrubyMan::GetInstance() ; \ 13 if(instance == NULL) { \ 14 return mrb_nil_value() ; \ 15 } 16 17mrb_value Method1(mrb_state *mrb, mrb_value self) { 18{ 19 GET_INSTANCE() ; 20 実際の処理~ 21 22 return23} // ←ここで終了処理が呼ばれる

【余談です】
RAIIって本当に便利ですよ。

ただ、終了処理をデストラクタで行うので、終了処理で例外を投げられないことが頭痛いです。
例外は処理をぶちっと中断できるのでたいへん便利です。(矛)
RAIIでその中断された時にもリソース開放できるので強力です。(盾)
ただし、例外処理中のデストラクタ内で例外を投げると、リソース開放処理の中断という矛盾が生じるわけです。

投稿2016/07/23 03:09

編集2016/07/23 03:16
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

drednote

2016/07/23 04:23

あぁ、スマートポインタなんかでやる手法ですね。RAIIという名前は恥ずかしながら不勉強で、知りませんでしたがスコープから抜けるときにデストラクタで色々終了処理を行うパターンは確かによくやりますね。クロージャに出来ないと外側のリソース開放出来ないから、リソース使うならScopeExitに全部管理させる必要がありますが、共通なのであれば問題無いでしょう。 例外については基本、起こったらプログラムを終了させるべきだと考えているのでリソースの開放漏れはそこまで問題にならないでしょう。 エラー処理に例外を使うのは間違った考え方だと思っているので、例外が起こるケースというのは大抵、プログラムが続行不可能だと思いますので。
Chironian

2016/07/23 04:43

汎用なscope_exitがC++17に提案されているそうですよ。関数オブジェクトを受け取れるのでラムダ式も受け取れます。boostにも入っているし、自分で書いても大したことないです。 > エラー処理に例外を使うのは間違った考え方だと思っている なるほど。そのような考え方もあるのですね。 私自身は、エラーチェック漏れしていないことのテストがあまりに憂鬱なので使う方です。 しかし、RAIIバターンとの相性が悪いので頭痛いです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問