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

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

新規登録して質問してみよう
ただいま回答率
85.49%
Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

イベントハンドラ

マウスのクリックなどの特定の事象(イベント)が発生した時に実行される処理のことをイベントハンドラと呼びます。

C++

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

Q&A

解決済

2回答

3563閲覧

C++のクラス内関数をタイマハンドラに渡したいができない

Kitano

総合スコア15

Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

イベントハンドラ

マウスのクリックなどの特定の事象(イベント)が発生した時に実行される処理のことをイベントハンドラと呼びます。

C++

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

0グッド

0クリップ

投稿2021/06/27 08:05

Linuxのタイマハンドラを利用してインターバルタイマを使おうと思ったのですがクラス内関数をハンドラに登録が出来ない事が分かり困っています。
下記に状況を再現したコードとコンパイルエラーの内容を添付します。

ライブラリの変更等も含めてこの状況を解決できる方法は何か無いでしょうか。

C++

1 2#include <sys/time.h> 3#include <signal.h> 4#include <string.h> 5#include <iostream> 6#include <time.h> 7 8 9class timer_test{ 10private: 11 struct sigaction action; 12 struct itimerval timer; 13 struct timespec timer_count, last_time_timer_count; 14 int counter = 0; 15 int nsec; 16 17public: 18 timer_test(){ 19 memset(&action, 0, sizeof(action)); 20 } 21 22 23 int timer_start(void){ 24 scheduler_setup(100000); 25 while(counter < 50){ 26 _nanosleep(0, 50); 27 timer.it_interval.tv_usec = 0; 28 timer.it_value.tv_usec = 0; 29 setitimer(ITIMER_REAL, &timer, NULL); 30 } 31 return 0; 32 } 33 34private: 35 int scheduler_setup(int interval_us){ 36 struct sigaction action; 37 // set signal handler 38 action.sa_handler = SignalHandler; 39 action.sa_flags = SA_RESTART; 40 sigemptyset(&action.sa_mask); 41 if(sigaction(SIGALRM, &action, NULL) < 0){ 42 std::cout << "sigaction error" << std::endl; 43 exit(1); 44 } 45 // set intarval timer 46 timer.it_value.tv_sec = 0; 47 timer.it_value.tv_usec = interval_us; 48 timer.it_interval.tv_sec = 0; 49 timer.it_interval.tv_usec = interval_us; 50 if(setitimer(ITIMER_REAL, &timer, NULL) < 0){ 51 std::cout << "setitimer error" << std::endl; 52 return 1; 53 } 54 clock_gettime(CLOCK_REALTIME, &last_time_timer_count); 55 return 0; 56 } 57 58 59 void SignalHandler(int signum){ 60 static unsigned long cnt = 1; 61 static unsigned long sum = 0; 62 static unsigned int ave = 0; 63 64 clock_gettime(CLOCK_REALTIME, &timer_count); 65 nsec = timer_count.tv_nsec - last_time_timer_count.tv_nsec; 66 if(nsec > 0){ 67 sum += nsec; 68 ave = sum / cnt; 69 cnt++; 70 } 71 std::cout << "SignalHandler:" << nsec / 1000 << "usec\n" << std::endl; 72 std::cout << "ave:" << ave / 1000 << "usec\n" << std::endl; 73 last_time_timer_count = timer_count; 74 counter++; 75 76 return; 77 } 78 79 80 int _nanosleep(int sec, int nsec){ 81 struct timespec req, rem; 82 req.tv_sec = sec; 83 req.tv_nsec = nsec; 84 rem.tv_sec = 0; 85 rem.tv_nsec = 0; 86 while(nanosleep(&req, &rem)){ 87 if(errno == EINTR){ 88 req.tv_sec = rem.tv_sec; 89 req.tv_nsec = rem.tv_nsec; 90 }else{ 91 perror("nanosleep error"); 92 return -1; 93 } 94 } 95 return 0; 96 } 97}; 98 99 100int main(void) 101{ 102 timer_test *t = new timer_test(); 103 t -> timer_start(); 104 return 0; 105} 106
$ g++ timer_test.cpp -std=c++11
timer_test.cpp: In member function ‘int timer_test::scheduler_setup(int)’: timer_test.cpp:38:27: error: cannot convert ‘timer_test::SignalHandler’ from type ‘void (timer_test::)(int)’ to type ‘__sighandler_t {aka void (*)(int)}’ action.sa_handler = SignalHandler; ^

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

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

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

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

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

guest

回答2

0

ベストアンサー

非メンバ関数とメンバ関数は型が異なります。 非メンバ関数を要求しているところにメンバ関数を渡すことは出来ません。

メンバ関数はオブジェクトとセットでなければ実行することが出来ない存在です。 この場合に SignalHandler を渡されたとして、どのオブジェクトを参照すればよいのか sigaction はわかりません。

SignalHandler を非メンバ関数になるように変形するか、 static で修飾する (事実上非メンバ関数に変形するのと手間は同じでしょう) か、あるいは std::mem_fnstd::bind など (あるいはラムダ式) を使ってオブジェクトとメンバ関数を結び付けてから渡すといったような対処が考えられます。

投稿2021/06/27 08:46

SaitoAtsushi

総合スコア5444

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

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

Kitano

2021/06/27 10:24

回答ありがとうございます。 「非メンバ関数とメンバ関数は型が異なります。 非メンバ関数を要求しているところにメンバ関数を渡すことは出来ません。」というのはその通りで説明が足りず、すみません。 今回SignalHandlerはメンバ変数にアクセスしているので非メンバ関数にするのは難しく、出来れば避けたいです。 そこで教えて頂いた「std::mem_fn や、std::bind 、ラムダ式」を用いてオブジェクトとメンバ関数を結び付けてから渡す方法を試してみたのですが結局エラーが出てしまいました。上記のプログラムの60行目のみ書き換えています。何か書き方が良く無かったのでしょうか。ご教授頂けると幸いです。 std::mem_fnの場合 書き換えた60行目の内容 action.sa_handler = std::mem_fn(&timer_test::SignalHandler); エラー文 timer_test.cpp: In member function ‘int timer_test::scheduler_setup(int)’: timer_test.cpp:61:27: error: cannot convert ‘std::_Mem_fn<void (timer_test::*)(int)>’ to ‘__sighandler_t {aka void (*)(int)}’ in assignment action.sa_handler = std::mem_fn(&timer_test::SignalHandler); std::bindの場合 書き換えた60行目の内容 action.sa_handler = std::bind(&timer_test::SignalHandler, this, std::placeholders::_1); エラー文 timer_test.cpp: In member function ‘int timer_test::scheduler_setup(int)’: timer_test.cpp:62:27: error: cannot convert ‘std::_Bind_helper<false, void (timer_test::*)(int), timer_test*, const std::_Placeholder<1>&>::type {aka std::_Bind<std::_Mem_fn<void (timer_test::*)(int)>(timer_test*, std::_Placeholder<1>)>}’ to ‘__sighandler_t {aka void (*) (int)}’ in assignment action.sa_handler = std::bind(&timer_test::SignalHandler, this, std::placeholders::_1); ラムダ式の場合 書き換えた60行目の内容 action.sa_handler = [](int n){this::SignalHandler(n);}; エラー文 timer_test.cpp: In lambda function: timer_test.cpp:63:39: error: ‘this’ was not captured for this lambda function action.sa_handler = [](int n){this::SignalHandler(n);};
SaitoAtsushi

2021/06/27 11:49

`std::mem_fn` はメンバ関数を関数オブジェクトにする関数ですが、オブジェクトと結びつける機能はないです。 `std::bind` は Callable なオブジェクト (関数ポインタや関数オブジェクト) に引数を適用する機能はありますが、メンバ関数は Callable ではないので使えません。 `std::mem_fn` と `std::bind` を組み合わせて使うということを意図していました。 ただし `std::bind` の出力は未規定の Callable オブジェクトであり、関数ではありませんのでそのままではハンドラに出来ません。 この方法を使う場合でも一旦は非メンバ関数 (または `static` なメンバ関数) を経由する必要はあります。 複雑な変形をするよりは非メンバ関数の中でこれらのことをすると少しは簡単になるかもしれないといった程度のことで、非メンバ関数を使わないという選択は不可能です。 ラムダ式内で `this` を使うには `this` をキャプチャしておく必要があります。 ラムダ式が生成する関数はキャプチャする変数がない場合に限って関数ポインタに型変換できるというルールがありますが、 `this` を使うとなるとラムダ式では関数ポインタを作れません。 つまりラムダ式を使う場合でも同様に非メンバ関数 (または `static` なメンバ関数) を作る必要があります。
Kitano

2021/06/27 12:42

詳しい説明ありがとうございます。 結局のところタイマハンドラに渡す関数をクラス外に出すか、staticにするしかないという事でしょうか。 となると本来のプログラムの方では大きく実装を変える必要があるのでどの様に変更すると関数をstaticにできるか検討し直してみます。 ありがとうございました。
guest

0

よくあるthunkをつくれば解決する話ではありますね。シグナルハンドラ自体がプロセスで固定個数なので、C++のクラス(インスタンス)と繋げるならグローバル変数経由でいいんじゃないかな。
・インスタンスをグローバル変数としておきコンストラクタなりtimer_startのタイミングでthisを代入しておきます。
(ここではclass timer_test *gtimer;とでも定義しましょうか。シングルトンにしたいとかはお好きなように。)
・gtimer->SignalHandler(signum)を呼ぶだけの、staticなハンドラ関数(thunk)を作る。
・sigactionにはthunkを登録する。

ただそうしてインスタンスとつなげたとしても、signal handlerには、async-signal-safeな関数しか呼べない、というキツい制限が有るんですよね、、、

投稿2021/06/29 12:42

matukeso

総合スコア1590

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.49%

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

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

質問する

関連した質問