C++ では std::function に依然として関数ポインターが必要なのはなぜですか?

C/C++ では、ポインターを使用してコードの一部を指すことができます。このポインターは関数ポインターと呼ばれます。次のようなコードがあるとします。

#include <stdio.h>

int func(int a) {
    
    
  return a + 1;
}

void main() {
    
    
   int (*f)(int) = func;
   printf("%p\n", f);
}

関数 func を定義し、ポインタ変数 f を使用して関数を指し、変数 f が指すアドレスを出力します。コードは非常に単純です。コードをコンパイルし、コンパイル後に生成された命令を確認します。 . func 関数に焦点を当てます。

0000000000400526 <func>:
  400526:       55                      push   %rbp
  400527:       48 89 e5                mov    %rsp,%rbp
  40052a:       89 7d fc                mov    %edi,-0x4(%rbp)
  40052d:       8b 45 fc                mov    -0x4(%rbp),%eax
  400530:       83 c0 01                add    $0x1,%eax
  400533:       5d                      pop    %rbp
  400534:       c3                      retq

コンパイルされた関数 func がアドレス 0x400526 にあることがわかります。このアドレスを覚えておきましょう。

次に、コンパイルされたプログラムを実行して、このコードが何を出力するかを考えてください。

明らかに、メモリ内の func 関数のアドレスである必要があります。

$ ./a.out
0x400526

ご想像のとおり、実際、関数ポインタも本質的にはポインタですが、このポインタが指すのはメモリ内のデータの一部ではなく、次のようにメモリ内のコードの一部です。ポインタを見てください。 「一般的に、
ここに画像の説明を挿入
それはメモリ内のデータの一部を指し、関数ポインタはメモリ内のコードの一部を指します。この例では、それはメモリ アドレス 0x400526 を指し、関数 func の機械語命令はここにあります」これで、関数ポインタについて理解できるようになりました。

関数ポインタの機能は、コードの一部を変数として渡すことであり、主な用途の 1 つはコールバック関数です。

コールバック関数は、実際には次のようにモジュール A で定義され、モジュール B で呼び出されます。
ここに画像の説明を挿入
ただし、そのようなシナリオが存在する場合もあります。それでもモジュール A で関数を定義する必要があり、関数 A の操作はデータに依存する必要があります。モジュール B によって生成された関数と、モジュール A によって定義された関数とモジュール B によって生成されたデータを、次のように C モジュールに渡して呼び出します。 このとき、関数ポインターは単にポイントしているだけなので、単純な関数ポインターでは十分ではありません
ここに画像の説明を挿入
。コードの一部、メモリ内のコードの一部だけでなく、メモリ内のデータの一部もモジュール C に渡す必要があります。この時点で、コードとデータをパッケージ化するための構造を定義できます。 、 このような:

typedef void (*func) (int);

struct closure{
    
    
  func f;
  int arg;    
};

この構造をクロージャと名付けました。この構造には 2 つの部分があることに注意してください。

コードを指すポインタ
変数 データを格納する
変数 このようにして、モジュール A のポインタ変数に値を割り当て、モジュール B のデータを格納する変数に値を割り当て、この構造体をモジュール C に渡します。このように使用されます:

void run(struct functor func) {
    
    
    func->f(func->arg);
}

つまり、クロージャには、コードの一部と、このコードで使用されるデータの両方が含まれています。ここでのデータは、コンテキスト (コンテキスト)、または環境 (環境) とも呼ばれます。これは、実際には関数が依存するデータです。ここに画像の説明を挿入
これが C++ の std::function の目的でもあります。

単純な関数ポインタにはコンテキストをキャプチャする機能がありません。ここでのコンテキストとは、コードが依存するデータを指します。コードが依存するコンテキストを格納する構造体を自分で構築する必要があります。

C++ では、関数ポインターにはオブジェクトのコンテキスト (オブジェクトへのポインター) をキャプチャする方法がないため、関数ポインターを単純に使用してオブジェクトのメンバー関数を指すことはできません。

std::function の役割は、本質的には先ほど定義した構造とそれほど変わりません。

std::function を使用すると、コードの一部を保存するだけでなく、必要なコンテキストを保存し、適切な場所でコンテキストに基づいてこのコードを呼び出すこともできます。

同時に、 std::function はより一般的であり、正しい関数シグネチャを持つ限り、これを使用して任意の呼び出し可能なオブジェクト (呼び出し可能なオブジェクト) を格納できます。

おすすめ

転載: blog.csdn.net/C214574728/article/details/127026324