C++クラスメンバ関数の関数ポインタの使い方

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
chibago

C++クラスメンバ関数の関数ポインタの使い方

#1

投稿記事 by chibago » 14年前

f(x)=0などを数値的に問題(ブレント法など解法は何でもよいのですが、)を解く際には
対象となる関数を自在に操作できると便利です。このような用途に使うために、
関数ポインタというものの存在を知り使ってみました。

メイン文のと一緒に対象となる関数を走り書きして、ブレント法のクラスのソルバー関数
の引数に対象となる関数のアドレスを入れてやれば、任意の関数が処理できて便利である
ことが実感できました。

動作するものができたことを確認して、いざ、本格的に使用することを考えたのですが、
思わぬ問題に突き当たりました。

ただの関数の場合は、戻り値の型 (ポインタ名*) (引数の型)だけでよく、任意の関数
が扱えて便利でしたが、クラスのメンバ関数の場合には、名前空間、クラス名も指定
しなければなりません。結局、ブレント法のクラスに使用が想定されるクラスのクラス名
を列挙するようなプログラムになってしまいました。

代替案でもよろしいので、もうちょっとスマートに扱える方法をご存知でしたら、
ご教授いただければと思います。

ISLe
記事: 2650
登録日時: 14年前
連絡を取る:

Re: C++クラスメンバ関数の関数ポインタの使い方

#2

投稿記事 by ISLe » 14年前

関数オブジェクトはいかがですか?

chibago

Re: C++クラスメンバ関数の関数ポインタの使い方

#3

投稿記事 by chibago » 14年前

ISLeさん、
ありがとうございます。
今、関数オブジェクトと言うものを勉強中です。

非常に難しく、はいそうですねと言うわけにもいかなそうです。
比較的単純で、用途に適している例を示したサイトをご存知でしたら
ご紹介いただけば助かります。

もうしばらく、勉強致したいと思います。

ISLe
記事: 2650
登録日時: 14年前
連絡を取る:

Re: C++クラスメンバ関数の関数ポインタの使い方

#4

投稿記事 by ISLe » 14年前

関数オブジェクト(ファンクタとも言います)は、簡単に言うと関数呼び出し演算子をオーバーライドしたクラスのインスタンスのことです。

コード:

#include<iostream>
int function_add(int a, int b)
{
	return a + b;
}
struct functor_add
{
	int operator()(int a, int b) const {
		return a + b;
	}
};
int main(void)
{
	int (*function)(int, int) = function_add; // 関数ポインタ
	functor_add functor; // 関数オブジェクト(ファンクタ)

	std::cout << function(12,34) << std::endl;
	std::cout << functor(56,78) << std::endl;

	return 0;
}
c++0xが使える処理系なら、ラムダ式のほうが走り書きに近いことができます。

chibago

Re: C++クラスメンバ関数の関数ポインタの使い方

#5

投稿記事 by chibago » 14年前

ISLe さん、
おへんじありがとうございます。
関数オブジェクトについては、いまいち良くできていませんが、
頂いたサンプルを自分のやりたいものに近づけてみました。

コード:

 #include<iostream>
struct functor_add
{
    int operator()(int a, int b) const {
        return a + b;
    }
};

int wrap_func(functor_add func, int a, int b ){
  return func(a, b);
}

int main(void)
{
    functor_add functor; // 関数オブジェクト(ファンクタ)
 
    std::cout << wrap_func(functor,56,78) << std::endl;
 
    return 0;
}

wrap_funcとしていますが、私のやりたいことはこの様に他の関数を受け取り、
f(x)=0を満たすxを算出(ここで言うwrap_funcの中でfunctor_addに当たるものを操作しながら)
することです。

関数オブジェクトを使うと、関数ポインタのように関数相当の演算を
他の関数に渡すことができることが分かりましたが、これだけだと関数ポインタ
よりも制限が多い(関数ポインタはアドレスと戻り値、引数の型を示せばよかったのに対し、
関数オブジェクトは定義(中身)も指定しなければならない?)ような気がします。

また、走り書きのようなことをしたいのではなく(これは、あくまでも動作確認です。)
任意のクラスのメンバ関数を取り込み操作したいと思っております。
(関数ポインタではこの段階で、クラス名の指定という制約が発生しました。)

関数オブジェクトを使いこなせれば、この様な問題にも対処できるのでしょうか。

ISLe
記事: 2650
登録日時: 14年前
連絡を取る:

Re: C++クラスメンバ関数の関数ポインタの使い方

#6

投稿記事 by ISLe » 14年前

chibago さんが書きました:(関数ポインタではこの段階で、クラス名の指定という制約が発生しました。)
どのような点を制約とお考えなのでしょうか。

ただの関数も特定の名前空間にあれば名前空間を指定する必要があります。
使用することが分かっている関数をクラス名も含めて列挙することに何か問題があるのでしょうか。

関数オブジェクトは関数内で定義できるとか、テンプレートで型に依存しないコードを書けるとか、ただの関数よりも制限はゆるいと思うのですが、役に立つかどうかは分かりません。

(追記)
テンプレートはただの関数にも有効でした。

chibago

Re: C++クラスメンバ関数の関数ポインタの使い方

#7

投稿記事 by chibago » 14年前

ISLe さん、
お返事ありがとうございます。

私は、C++に関して全般的には理解できていませんので、
ISLeさんから頂いた情報が整理できていないのだと思います。

当初、私が意図していたのは、汎用的な演算処理を行うソルバークラス
に対し、解かれる関数(方程式)をもったクラスのメンバ関数をソルバー
クラスのメンバ関数の引数として渡して解くと言うようなものです。
その後、関数ポインタの存在を知り、生の関数の処理に成功し、
次に、クラスの保持するメンバ関数を処理しようとしたところ、
クラス名の指定が必要になることに気づきました。

ISLe さんのご指摘のように、この場合に生の関数が他の名前空間に
所属するさいには、その指定が必要になり、本質的には変わらないのかも
しれません。

関数オブジェクトの場合にも、このことは基本的に同じである
と理解しております。(理解はできておりませんが、ISLe さんの
コメントよりそう判断いたしました。)

ただ、利用するソルバークラスに対し、利用側のクラスの情報を
一々登録するのは、避けられるべきだとおもいます。
(おそらく、この様な演算処理を行うライブラリが存在する
と思いますが、ライブラリ側をその都度変更してくださいと
言うものは無いと思われます。)

このような、憶測で、一番ありそうな関数ポインタまわりの
検討をおこなっておりましたが、もしかしたら、このアプローチ
が見当違いだったのかもしれません。

可能性があるとすれば、関数ポインタのクラス指定に関して、
アクセスをするクラスの基底クラスを統一して、ポリモフィズム
で対応するか、ソルバークラスを継承するか(この場合は、
対象となる関数名と型を統一する必要があると思います。)
しかないのではないかと思い始めております。

アバター
GRAM
記事: 164
登録日時: 14年前
住所: 大阪

Re: C++クラスメンバ関数の関数ポインタの使い方

#8

投稿記事 by GRAM » 14年前

どういう解決法をとるかによってやりたいことが変わってくると思います。

自分の思いつく限りだと
①ソルバークラスをテンプレートクラスにしてしまい、そのテンプレート引数で使用する関数オブジェクトを指定する
②そうでなく、ソルバークラスのソルバ関数本体を関数テンプレートにし、operator()をもつ(別にoperator()でなくてもよいですが要は同名の関数を持つ)
関数オブジェクトを引数にとるようにする。
③関数オブジェクトをすべて同一の基底クラスから派生させ、ソルバー関数はその基底クラスのポインタをとるようにする。
基底クラスの仮想関数でf(x)にあたる関数を定義し、その実装を派生クラスでオーバーライドする。
④必要性の薄い解法ですがDelegateを使う
ほかのサイトの紹介なのですが
○×さんのホームページでDelegateというもののわかりやすい説明があります。
⑤tr1::functionを引数にとるようにし、tr1::mem_fnと、std::bind1stなどを併用してソルバ関数へ渡す。
tr1::functionなんかの説明は
http://www.kmonos.net/alang/boost/function.html
あたりが有名かなぁと。
自分ならばおそらく⑤の解決法をとるかと思います。

chibago

Re: C++クラスメンバ関数の関数ポインタの使い方

#9

投稿記事 by chibago » 14年前

GRAMさん、
ご紹介ありがとうございます。
私も、boostを使うのがよさそうだと思いました。

ただ、使い方が(ドキュメントを読んでも)良くわからないので
ご指導いただければ助かります。

やることとしては、Solverクラスのcalcメソドの
引数にDataクラスのget_valueメソドを渡すという処理です。
ただし、get_valueメソドはDataクラスで純粋に定義された
ものではなく、その基底クラスあるDataBaseクラスの
publicのメンバ関数です。(それぞれ、テンプレートで記述し
ております。)また、MyNameという名前空間においております。

初めに、Solverクラスのcalcメソドですが、

コード:

T calc(boost::funciton<T (T)> func, const T target, const T lower, const T upper);
T calc_two_arg(boost::function<T (T, T)> func, const T target, const T lower, const T upper,
                     const T other);
このように、関数ポインタの宣言をboost::functionに置き換えました。
これ自体は、生の関数のアドレスを代入してテストしたところ、
予想通りの挙動が得られております。
calc_two_argは受け取る関数の引数が2つの場合のものです。
関数ポインタを使用していた時には、オーバーロードで勝手に
切り替わってくれたのですが、boost::functionに切り替えてからは、
オーバーロードが使えず、結局、関数名を変更いたしました。

次が苦戦しているところですが、
Dataクラス内での呼び出しですが、

コード:

MyName::Solver<T> solv;
T result = solv.calc_two_arg(boost::men_fn(&MyName::DataBase::get_value),
                                         target, lower, upper, other);
としておりますが、型がミスマッチだとの趣旨のエラーが出てしまいます。
boost::men_fnはクラスメソドのアドレスを生の関数のアドレスのように変換してくれる
ものだと理解しておりますが、何が間違っているのでしょうか。

すみませんが、ご教授いただければ幸いです。

アバター
GRAM
記事: 164
登録日時: 14年前
住所: 大阪

Re: C++クラスメンバ関数の関数ポインタの使い方

#10

投稿記事 by GRAM » 14年前

メンバ関数のポインタは、それを呼び出すインスタンスも指定してやらなければなりません。
std::bind1stなどを使えばいいのですが、引数が二つ以上あるとなるとstd::bindを使うのがよい解法となるかと。
(実際にはtr1::bindやboostなどでもよいですがVSなどは0xの機能先取りとして実装されています)

おそらくそこそこ美しい解法だと思います。

コード:

#include <functional>
#include <iostream>
using namespace std;

//ソルバーよくわからなかったので実装は適当です^^;
template <typename T>
class Slover{
public:
	T calc( std::function<T (T)> func, const T target, const T lower, const T upper){
		return func(1.0);
	}

	T calc( std::function<T (T, T)> func, const T target, const T lower, const T upper, const T other){
		return func(1.0, 1.0);
	}
};


//引数二つの関数をもつクラス
struct BF{
	double f( double lhs, double rhs ){
		return lhs+rhs;
	}
};

//引数一つの関数をもつクラス
struct UF{
	double f( double x ){
		return x;
	}
};

int main(){
	Slover<double> solv;
	BF bf;
	UF uf;

	using namespace std::placeholders;
	cout << 
		solv.calc( 
			std::bind( &BF::f, &bf, _1, _2 ),
			0.0, 0.0, 0.0, 0.0
		)
		<< endl;


	cout <<
		solv.calc(
			std::bind( &UF::f, &uf, _1 ),
			0.0, 0.0, 0.0
		)
		<< endl;

	return 0;
}

bindには、関数を呼び出すインスタンスを隠された引数(thisポインタ)として第一引数に渡し、
実際の第一引数(_1)を第二引数へ、実際の第二引数(_2)を第三引数へ渡してやるよう指示します。
_1や_2はプレースメントホルダーと呼ばれるもので、要は実際にfunctionに渡される引数です。

chibago

Re: C++クラスメンバ関数の関数ポインタの使い方

#11

投稿記事 by chibago » 14年前

GRAM さん
ご丁寧な解説ありがとうございます。
bindは引数をアレンジしたい時に使うものかと思っておりました。

寧ろboost::mem_fnは必須ではなかったようです。
(私の場合は、クラスのメンバ関数を渡していたため、
必要かと思っていたのですが)

また、私の場合は使用したいクラスからよびだし
ているため、インスタンスはthisとしております。

大変苦戦しておりましたので、非常に助かりました。

閉鎖

“C言語何でも質問掲示板” へ戻る