【C++ / Boost】boost::bindについての適当まとめ

何語だこれ…状態になったのでメモしておきます。

まずはboost::functionから。

#include <iostream>
#include <boost/function.hpp>

typedef boost::function<int(int, int)> TestFunction;

int sum(int a, int b)
{
	return a + b;
}

int main()
{
	TestFunction Tf;
	int Result = 0;

	Tf = &sum;
	Result = Tf(2, 5);
	
	std::cout << "結果は" << Result << "でした。" << std::endl;
	return 0;
}

こんな感じです。Resultは7になります。このboost:functionを使用することで、簡単に関数ポインタに近い働きをさせることができます。
また、他にも器用な点があります。関数ポインタの場合、上記の例のように普通の関数の場合なら何の問題もないのですが、クラス内の関数のポインタとなると、うまく扱うことができません。具体的には、

// doubleを戻り値とし、intを引数とする関数のポインタ。
double (*pFunc)(int) = &func;
// doubleを戻り値とし、intを引数とするCSampleメンバ内の関数のポインタ。
double (CSample::*pFunc)(int) = &CSample::func;

のように、戻り値の型が違うために、不便となるケースがでてきます。
この問題点をboost::functionは解決してくれる、というわけです。
具体的には以下のようになります。

#include <iostream>
#include <boost/bind.hpp>
#include <boost/function.hpp>

typedef boost::function<int()> TestFunction;

class Sample
{
public:
	Sample()
	{
		Val = 5;
	}
	int GetVal()
	{
		return Val;
	}
	int Val;
};

int main()
{
	TestFunction Tf;
	Sample Smp;
	int Result = 0;

	// ここでboost::bindを使用しています。
	Tf = boost::bind(&Sample::GetVal, Smp);
	Result = Tf();
	
	std::cout << "結果は" << Result << "でした。" << std::endl;
	return 0;
}

ここでboost::bindが出てきます。第一引数が実行する対象の関数、第二引数が対象のクラスです。メンバ関数は(当然ですが)自身のメンバにアクセスしたりする関係上、そのクラスが存在してないと動きませんので、その対象となるクラスを指定します。
ちなみに、メンバ関数のポインタではなく、普通の関数のポインタも取れます。

#include <iostream>
#include <boost/bind.hpp>
#include <boost/function.hpp>

typedef boost::function<int()> TestFunction;

int GetVal()
{
	return 10;
}

int main()
{
	TestFunction Tf;
	int Result = 0;

	// ここでboost::bindを使用しています。
	// ここは Tf = &GetVal; でも動作します。
	Tf = boost::bind(&GetVal);
	Result = Tf();
	
	std::cout << "結果は" << Result << "でした。" << std::endl;
	return 0;
}

boost::bindを使用して引数がある関数を扱う場合、少々ややこしくなります。

#include <iostream>
#include <boost/bind.hpp>
#include <boost/function.hpp>

typedef boost::function<int(int, int)> TestFunction;

int Sum(int a, int b)
{
	return a + b;
}

int main()
{
	TestFunction Tf;
	int Result = 0;

	// ここでboost::bindを使用しています。
	// 引数に_1, _2が使われています。
	Tf = boost::bind(&Sum, _1, _2);
	Result = Tf(2, 5);
	
	std::cout << "結果は" << Result << "でした。" << std::endl;
	return 0;
}

コメントで説明しているように、引数に_1や_2が使われています。これは「関数を実行するときに引数として渡しますよ」という目印です。しかし、なぜこのような回りくどいことをしなければならないのでしょう。
それは、「引数を固定できる」機能を持っているからです。…ちょっと何言ってるか分かりませんね。コードを見てみます。

#include <iostream>
#include <boost/bind.hpp>
#include <boost/function.hpp>

// <int(int, int)>ではなく<int(int)>となっている。
typedef boost::function<int(int)> TestFunction;

int Sum(int a, int b)
{
	return a + b;
}

int main()
{
	TestFunction Tf;
	int Result = 0;

	// _1, _2だったのが4, _1となっている。
	// これは第一引数を4で固定したこととなる。
	Tf = boost::bind(&Sum, 4, _1);
	// 引数の数が1つになっている。
	Result = Tf(5);
	
	std::cout << "結果は" << Result << "でした。" << std::endl;
	return 0;
}

boost::bind関数が(_1, _2)ではなく、(4, _1)となっています。本来Sum()関数は引数が2つ必要ですが、最初に「1つ目は4を入れてしまおう」と決め打ちすることで、2つ必要な変数入力を1つに抑えています。今回の例だと有り難みは分かりにくいですが、実際にはこの技術を応用することでかなり複雑なコードを書くことができます。
また、boost::functionの<>の中身が先ほどまでと違います。これは、引数が一つで済むからです。

このように、使いこなせれば便利ですが、エラーの中身が意味分からないという欠点もあります。例えば、上記のコードで言えば、

Tf = boost::bind(&Sum, 4, _1);

Tf = boost::bind(&Sum, 4, _2);

と書いてしまうと

error C2118: 添字が負の数です。

と文句を言われます。ですが、もちろん添字と理解して使っているわけではないので、エラーの発生源がとても特定しにくくなる欠点が存在します。