C++においてメンバ関数を関数オブジェクトとして渡す方法 - std::bindとラムダ式

# 概要

この記事では、C++のメンバ関数を関数オブジェクトとして渡す方法を紹介します。具体的には、std::bindを用いた方法とラムダ式を用いた方法を、それぞれソースコードを交えて説明します。

# はじめに

初心者がC++プログラムを作成する際、自作クラスのメンバ関数をあるライブラリのクラスやAPIに渡すことが求められる場面があります。しかし、メンバ関数は直接渡すことができず、この点はC++の扱いにくさの一つとなっています。これは、メンバ関数がクラスのインスタンスに関連付けられているため、引数としてそのまま渡すことができないためです。

幸い、C++では「std::bind」と「ラムダ式」という機能を使ってこの問題を回避できます。これらの機能を利用することで、メンバ関数を適切に渡すことができ、C++の扱いにくさを少しでも軽減することができます。

# 具体的な実装方法

## std::bindを利用する方法

std::bindを用いることで、メンバ関数を関数オブジェクトに変換し、引数として渡すことができます。ただし、std::bindを使用する際には、関数のシグネチャや引数の順番に注意が必要です。

```cpp
#include <iostream>
#include <functional>

class MyClass {
public:
    void print(int x) {
        std::cout << "MyClass::print: " << x << std::endl;
    }
};

int main() {
    MyClass my_instance;
    auto bound_function = std::bind(&MyClass::print, &my_instance, std::placeholders::_1);

    bound_function(42);

    return 0;
}
```

## ラムダ式を利用する方法

ラムダ式を使用することで、メンバ関数を関数オブジェクトに変換し、引数として渡すことができます。ラムダ式はC++11以降で利用可能で、より簡潔に記述することができます。

```cpp
#include <iostream>

class MyClass {
public:
    void print(int x) {
        std::cout << "MyClass::print: " << x << std::endl;
    }
};

int main() {
    MyClass my_instance;
    auto lambda_function = [&my_instance](int x) { my_instance.print(x); };

    lambda_function(42);

    return 0;
}
```

この例では、ラムダ式を使ってMyClassクラスのprintメンバ関数を関数オブジェクトに変換し、lambda_functionとして保存しています。ラムダ式は、[&my_instance]というキャプチャリストでmy_instanceを参照し、(int x)という引数を受け取り、{ my_instance.print(x); }という処理を行います。

# クラスのメンバ関数内でそのクラスのメンバ関数を登録する

ここでは、ライブラリのクラスをメンバ変数として持ち、コールバック関数やイベント用の関数を登録する必要がある状況について解説します。この場合も、std::bindとラムダ式を使用して解決できます。

## std::bindを利用する方法

```cpp
#include <iostream>
#include <functional>

class LibraryClass {
public:
    void setCallback(std::function<void(int)> callback) {
        callback_ = callback;
    }

    void triggerEvent(int value) {
        callback_(value);
    }

private:
    std::function<void(int)> callback_;
};

class MyClass {
public:
    MyClass() {
        library_class_.setCallback(std::bind(&MyClass::eventHandler, this, std::placeholders::_1));
    }

    void eventHandler(int x) {
        std::cout << "MyClass::eventHandler: " << x << std::endl;
    }

    void testEvent() {
        library_class_.triggerEvent(42);
    }

private:
    LibraryClass library_class_;
};

int main() {
    MyClass my_instance;
    my_instance.testEvent();

    return 0;
}
```

この例では、MyClassのコンストラクタ内でstd::bindを使ってeventHandlerメンバ関数をライブラリのLibraryClassに登録しています。std::bindの引数には、メンバ関数のポインタ、thisポインタ、およびプレースホルダを指定しています。

## ラムダ式を利用する方法

```cpp
#include <iostream>
#include <functional>

class LibraryClass {
public:
    void setCallback(std::function<void(int)> callback) {
        callback_ = callback;
    }

    void triggerEvent(int value) {
        callback_(value);
    }

private:
    std::function<void(int)> callback_;
};

class MyClass {
public:
    MyClass() {
        library_class_.setCallback([this](int x) { eventHandler(x); });
    }

    void eventHandler(int x) {
        std::cout << "MyClass::eventHandler: " << x << std::endl;
    }

    void testEvent() {
        library_class_.triggerEvent(42);
    }

private:
    LibraryClass library_class_;
};

int main() {
    MyClass my_instance;
    my_instance.testEvent();

    return 0;
}
```

この例では、MyClassのコンストラクタ内でラムダ式を使ってeventHandlerメンバ関数をライブラリのLibraryClassに登録しています。ラムダ式は、[this]というキャプチャリストでthisポインタをキャプチャし、(int x)という引数を受け取り、{ eventHandler(x); }という処理を行います。

どちらの方法でも、MyClassのメンバ関数をライブラリのクラスに登録し、コールバック関数やイベント用の関数として使用できます。

# Staticメンバ関数を登録する方法

クラスのメンバ関数がstaticである場合、通常の関数と同様に扱うことができるため、関数オブジェクトとして登録することができます。staticメンバ関数は、クラスのインスタンスに紐づかず、クラス自体に紐づくため、インスタンスを必要とせずに呼び出すことができます。これにより、特別な処理を行わずとも関数オブジェクトとして扱うことができます。

以下に、staticメンバ関数を関数オブジェクトとして登録する例を示します。
```cpp
#include <iostream>
#include <functional>

class MyClass {
public:
    static void staticMemberFunction() {
        std::cout << "Static member function called." << std::endl;
    }
};

int main() {
    std::function<void()> func = MyClass::staticMemberFunction;
    func(); // 出力: Static member function called.
    return 0;
}
```
この例では、MyClassクラスのstaticメンバ関数staticMemberFunctionを、std::functionを使って関数オブジェクトとして扱っています。その後、func()として呼び出すことで、staticMemberFunctionが実行されます。このように、staticメンバ関数であれば、直接関数オブジェクトとして登録することができるため、std::bindやラムダ式を用いる必要はありません。

# まとめ
C++の初心者にとって、メンバ関数を関数オブジェクトとして渡す必要がある場面は扱いにくいものの一つです。しかし、C++のstd::bindやラムダ式を使えば、この問題をある程度解決できます。本記事では、一般的なシーンと、クラスのメンバ関数内でそのクラスのメンバ関数を登録するシーンにおいて、それぞれの方法をソースコードを交えて解説しました。これらの概念を理解し、具体的な例を通じて学ぶことで、C++の扱いにくさを少しでも和らげることができるでしょう。