[C++11] ラッパー

C++11 - ラッパー


ここに画像の説明を挿入

1. 関数ラッパー

1. 関数ラッパーの導入

次のコード行を見てください。

ret = func(x);

上記の機能は何でしょうか?func は関数名でしょうか?関数ポインタ? 関数オブジェクト (ファンクター オブジェクト)? また、ラムダ式オブジェクトでしょうか? したがって、これらは呼び出し可能な型です! このようなリッチ タイプは非効率的なテンプレートにつながる可能性があります。なぜ?読み進めましょう

template <class F, class T>
T useF(F f, T x)
{
     
     
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x);
}

double f(double i)
{
     
     
	return i / 2;
}

struct Functor
{
     
     
	double operator()(double d)
	{
     
     
		return d / 3;
	}
};

int main()
{
     
     
	//函数名
	cout << useF(f, 2.2) << endl;
	//函数对象
	cout << useF(Functor(), 2.2) << endl;
	//lambda表达式
	cout << useF([](double d)->double{
     
      return d / 4; }, 2.2) << endl;
	return 0;
}

画像-20230418172806929

上記のプログラム検証により、useF 関数テンプレートが 3 回インスタンス化されていることがわかります。各カウントのアドレスは異なります。useF 関数テンプレートのコピーを 1 つだけインスタンス化する方法はありますか? 呼び出し可能な型を統一するために、C++11 では関数ラッパーを導入して問題を解決しています。


2. 関数ラッパーの概念

関数ラッパーは、アダプターとも呼ばれます。C++ の関数は、基本的にクラス テンプレートとラッパーです。

関数クラス テンプレートのプロトタイプは次のとおりです。

//std::function在头文件<functional>
// 类模板原型如下
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;

テンプレート パラメータの説明:

  • Ret : 呼び出された関数の戻り値の型
  • Args...: 呼び出された関数の仮パラメータ

関数ラッパーを使用すると、関数ポインター (関数名)、ファンクター (関数オブジェクト)、ラムダ式、クラスのメンバー関数など、呼び出し可能なオブジェクトをラップできます。例:

#include <functional>
int f(int a, int b)
{
     
     
	return a + b;
}
struct Functor
{
     
     
public:
	int operator() (int a, int b)
	{
     
     
		return a + b;
	}
};
class Plus
{
     
     
public:
	static int plusi(int a, int b)
	{
     
     
		return a + b;
	}
	double plusd(double a, double b)
	{
     
     
		return a + b;
	}
};
int main()
{
     
     
	// 函数名(函数指针)
	std::function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;//3
	// 仿函数
	std::function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;//3
	// lambda表达式
	std::function<int(int, int)> func3 = [](int a, int b) {
     
     return a + b; };
	cout << func3(1, 2) << endl;//3
	//类的静态成员函数
	std::function<int(int, int)> func4 = Plus::plusi;//非静态成员函数必须加&,静态可不加
	cout << func4(1, 2) << endl;//3
	//类的非静态成员函数
	std::function<double(Plus, double, double)> func5 = &Plus::plusd;//非静态成员函数必须加&,静态可不加
	cout << func5(Plus(), 1.1, 2.2) << endl;//3.3
	return 0;
}

知らせ:

  1. クラスのメンバー関数をラップするときは、必ずクラス ドメインを指定してください。
  2. クラスの非静的メンバー関数は & を追加する必要があり、静的メンバー関数は追加してもしなくてもかまいません
  3. クラスの非静的メンバー関数には暗黙の this ポインターがあり、パッケージ化時に追加のパラメーター (クラス名) を追加し、呼び出し時にクラスの匿名オブジェクトを渡す必要があります。

3. 関数ラッパーを使用して逆ポーランド式の評価を最適化する

leetcode でそのような質問を見てください:逆ポーランド式評価

この問題を解決する手順は次のとおりです。

  • スタックを定義し、この配列をトラバースします
  • 数値にトラバースし、スタックに直接プッシュします
  • トラバースされた文字列が数値 (「+」、「-」、「*」、「/」のいずれか) でない場合、スタックの一番上にある 2 つの要素を取得して、対応する算術演算を実行し、結果をプッシュします。スタックに。
  • トラバーサルの最後に、最終スタックの一番上にある要素が最終値です

コードは以下のように表示されます:

class Solution {
     
     
public:
    int evalRPN(vector<string>& tokens) {
     
     
        stack<int> st;
        for (int i = 0; i < tokens.size(); i++)
        {
     
     
            if (!(tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/"))
            {
     
     
                st.push(stoi(tokens[i]));
            }
            else
            {
     
     
                int num1 = st.top();
                st.pop();
                int num2 = st.top();
                st.pop();
                if (tokens[i] == "+")
                    st.push(num1 + num2);
                else if (tokens[i] == "-")
                    st.push(num2 - num1);
                else if (tokens[i] == "*")
                    st.push((long)num1 * num2);
                else if (tokens[i] == "/")
                    st.push(num2 / num1);
            }
        }
        return st.top();
    }
};
  • 上記のコードでは、主に if ステートメントと else ステートメントを使用して、実行する操作の種類を決定しています. 後続の操作の種類が増えると、if ステートメントと else ステートメントを追加する必要があり、これは多少冗長です. ラッパーを使用して、このコードを最適化してください。

最適化ルールは次のとおりです。

  1. ここでは、ラッパーを使用して、演算子と対応する関数の間のマッピング関係を確立できます. この手順では、マップ コンテナーを使用して、リストの初期化で各演算子に対応する関数のマッピング関係を完了する必要があります.
  2. 配列をトラバースして、キャラクターがマップ コンテナー内にあるかどうかを判断します。そうである場合は、オペレーターであることを意味します。スタックの一番上にある 2 つの要素を取得し、対応する操作を実行し、結果をスタックにプッシュします。
  3. キャラクターがマップ コンテナーにない場合、それはオペランドであることを意味し、スタックに直接プッシュされます。
  4. トラバーサルが終了した後、スタックの一番上にある要素が直接返されます
  5. スタック オーバーフローを防ぐために、long long 型を使用することに注意してください。
class Solution {
     
     
public:
    int evalRPN(vector<string>& tokens) {
     
     
        stack<long long> st;
        map<string, function<long long(long long, long long)>> opFuncMap = 
        {
     
     
            {
     
     "+", [](long long x, long long y){
     
     return x + y;}},
            {
     
     "-", [](long long x, long long y){
     
     return x - y;}},
            {
     
     "*", [](long long x, long long y){
     
     return x * y;}},
            {
     
     "/", [](long long x, long long y){
     
     return x / y;}}
        };
        for (auto& str : tokens)
        {
     
     
            if (opFuncMap.count(str))//判断操作符是否在opFuncMap
            {
     
     
                long long right = st.top();
                st.pop();
                long long left = st.top();
                st.pop();
                st.push(opFuncMap[str](left, right));
            }
            else//操作数
            {
     
     
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

4.関数ラッパーを使用して、テンプレートの効率が低いという問題と複数のコピーのインスタンス化を解決します

最初にラッパーを導入したとき、異なる型を渡して 3 つのコピーをインスタンス化することを提案したため、テンプレートの効率が低下しました.今では、関数ポインター、ラムダを直接渡すことはなくなりました...代わりに、ラッパーでラップすることで解決しました問題:

#include <functional>
template<class F, class T>
T useF(F f, T x)
{
     
     
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x);
}
double f(double i)
{
     
     
	return i / 2;
}
struct Functor
{
     
     
	double operator()(double d)
	{
     
     
		return d / 3;
	}
};
int main()
{
     
     
	// 函数名
	std::function<double(double)> func1 = f;
	cout << useF(func1, 11.11) << endl;
	// 函数对象
	std::function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;
	// lamber表达式
	std::function<double(double)> func3 = [](double d)->double {
     
      return d / 4; };
	cout << useF(func3, 11.11) << endl;
	return 0;
}

画像-20230418174438896

このときインスタンス化されたコピーは 1 つだけで、count のアドレスは常に同じであるため、静的メンバー変数は自然に蓄積されます。ラッパーによって型が統一されていることがわかります。


5. 関数ラッパーの意味

  • 呼び出し可能なオブジェクトのタイプを統一して、それらを一様に管理できるようにします。
  • パッケージ化後、呼び出し可能オブジェクトの戻り値と仮パラメーターの型が明確になり、ユーザーがより使いやすくなります。

2、バインド ラッパー

1.バインドラッパーの導入

次のコードを見てください。

#include <functional>
int f(int a, int b)
{
     
     
	return a + b;
}
struct Functor
{
     
     
public:
	int operator() (int a, int b)
	{
     
     
		return a + b;
	}
};
class Plus
{
     
     
public:
	Plus(int x = 2)
		:_x(x)
	{
     
     }
	int plusi(int a, int b)
	{
     
     
		return (a + b) * _x;
	}
private:
	int _x;
};

非静的メンバー関数には暗黙的な this ポインターがあるため、plusi 関数をラップするときに、もう 1 つのパラメーター (合計 3 つ) が渡されます. これは前に述べたことですが、Functor と f は 2 つのパラメーターを渡すだけで済みます. 次のことができます:

int main()
{
     
     
	// 函数名(函数指针)
	std::function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;//3
	// 仿函数
	std::function<int(int, int)> func2 = Functor();
	cout << func2(10, 20) << endl;//30
	//类的非静态成员函数
	std::function<double(Plus, int, int)> func3 = &Plus::plusi;//非静态成员函数必须加&,静态可不加
	cout << func3(Plus(), 100, 200) << endl;//300.22
}

ラッパーを使用して、文字列と対応する関数の間のマッピング関係を確立し、それをマップ コンテナーに配置するとします.このとき、問題が発生します:メンバー関数には 3 つのパラメーターがあり、マップ内の値の位置コンテナーでは、2 つのパラメーターのみを渡すことができます。

画像-20230427200302431

この問題を解決するには、以下のバインド ラッパーを使用する必要があります。


2.バインドラッパーの概念

バインド ラッパーの概念:

  • std::bind 関数は、ヘッダー ファイル <functional> で定義され、関数テンプレートです. これは、呼び出し可能なオブジェクトを受け入れ、新しい呼び出し可能なオブジェクトを生成して "adapt" する関数ラッパー (アダプター) のようなものです。元のオブジェクト。一般的に言えば、最初に N 個のパラメーターを受け取った関数 fn を取得し、いくつかのパラメーターをバインドすることにより、M 個のパラメーターを受け取る新しい関数を返すために使用できます (M は N より大きくてもかまいませんが、そうするのは意味がありません)。 . 同時に、std::bind 関数を使用すると、パラメーターの順序や数の調整などの操作も実装できます。

バインド関数テンプレートのプロトタイプは次のとおりです。

template <class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);
template <class Ret, class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);

テンプレート パラメータの説明:

  • fn: 呼び出し可能なオブジェクト
  • args...: バインドする引数のリスト (値またはプレースホルダー)

bind を呼び出す一般的な形式:

auto newCallable = bind(callable,arg_list);

説明:

  • callable: ラップする必要がある呼び出し可能なオブジェクト。
  • newCallable: それ自体が呼び出し可能なオブジェクトです
  • arg_list: は、指定された callable のパラメーターに対応するパラメーターのコンマ区切りリストです。newCallable を呼び出すと、newCallable は callable を呼び出し、arg_list でパラメーターを渡します。

The parameters in arg_list may include names of the form _ n, where n is an integer. これらのパラメータは、newCallable のパラメータを表す「プレースホルダ」であり、newCallable に渡されるパラメータの「位置」を占めます。値 n は、結果の呼び出し可能オブジェクト内のパラメーターの位置を示します。_1 は newCallable の最初のパラメーター、_2 は 2 番目のパラメーターなどです。


3. バインド ラッパーがバインドし、パラメーターの数を調整します。

通常の関数のバインド:

int Plus(int a, int b)
{
     
     
	return a + b;
}
int main()
{
     
     
	//表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
	std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
    	//auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
	cout << func1(1, 2) << endl;//3
}
  • バインド時、最初のパラメータは関数ポインタの呼び出し可能オブジェクトに渡されますが、バインドされる後続のパラメータ リストは placeholders::_ 1 と placeholders::_ 2 であり、これは、新しく生成された呼び出し可能オブジェクトが後続で呼び出されることを意味しますを呼び出すと、最初に渡されたパラメーターが placeholders::_ 1 に渡され、2 番目に渡されたパラメーターが placeholders::_ 2 に渡されます。このとき、バインド後に生成された新しい呼び出し可能オブジェクトのパラメーターの受け渡し方法は、バインドされていない元の呼び出し可能オブジェクトと同じであり、意味のないバインディングとも言えます。

パラメータの数を調整します。

  • Plus 関数の 2 番目のパラメーターを 20 に固定できます。パラメーター リストの placeholders::_2 を 20 に設定するだけです。このとき、バインド後に新しく生成された呼び出し可能オブジェクトを呼び出すときに、パラメーターを渡すだけで済みます。次のように:
int Plus(int a, int b)
{
     
     
	return a + b;
}
int main()
{
     
     
	//表示绑定函数 plus 的第2个参数为20
    std::function<int(int)> func2 = std::bind(Plus, placeholders::_1, 20);
	cout << func2(5) << endl;//25
}

バインディング メンバー関数:

class Plus
{
     
     
public:
	Plus(int x = 2)
		:_x(x)
	{
     
     }
	int plusi(int a, int b)
	{
     
     
		return (a + b) * _x;
	}
private:
	int _x;
};
int main()
{
     
     
	//未绑定,需要传3个参数
	std::function<int(Plus, int, int)> func1 = &Plus::plusi;
	cout << func1(Plus(), 100, 200) << endl;//600
	//绑定后,仅需传2个参数
	std::function<int(int, int)> func2 = std::bind(&Plus::plusi, Plus(10), placeholders::_1, placeholders::_2);
	cout << func2(100, 200) << endl;//3000
	//绑定指定参数,func3的第一个参数已被指定,仅需传1个参数
	std::function<int(int)> func3 = std::bind(&Plus::plusi, Plus(10), 15, placeholders::_1);
	cout << func3(25) << endl;//400
	return 0;
}
  • func1、func2、および func3 を比較すると、私の func1 が直接パッケージ化されていることは明らかであり、3 つのパラメーターを渡す必要があります (そのうちの 1 つは暗黙の this ポインターです)。私は Plus() をバインドして呼び出します 匿名オブジェクトをこのポインターに渡すと役立つ場合があるため、2 つのパラメーターのみを渡す必要があります; 私の func3 は指定されたパラメーターをバインドして調整するためのもので、func3 の最初のパラメーターは次のように指定されています。 15 であり、1 つのパラメーターのみを渡す必要があります。

最初にバインドを導入すると、パラメーターの数が一致しないという問題を解決しましょう。

画像-20230427200302431

この問題を解決するには、バインディングを使用してパラメーターの数を調整するだけです。

int f(int a, int b)
{
     
     
	return a + b;
}
struct Functor
{
     
     
public:
	int operator() (int a, int b)
	{
     
     
		return a + b;
	}
};
class Plus
{
     
     
public:
	Plus(int x = 2)
		:_x(x)
	{
     
     }
	int plusi(int a, int b)
	{
     
     
		return (a + b) * _x;
	}
private:
	int _x;
};
int main()
{
     
     
	map<string, std::function<int(int, int)>> opFuncMap =
	{
     
     
		{
     
      "普通函数指针", f },
		{
     
      "函数对象", Functor()},
		{
     
      "成员函数指针", std::bind(&Plus::plusi, Plus(10), placeholders::_1, placeholders::_2)}
	};
	cout << opFuncMap["普通函数指针"](1, 2) << endl;//3
	cout << opFuncMap["函数对象"](10, 20) << endl;//30
	cout << opFuncMap["成员函数指针"](100, 200) << endl;//3000
	return 0;
}

この時点で、メンバー関数ポインターのバインドが調整された後、つまり Plus() がバインドされ、呼び出し時に、匿名オブジェクトを this ポインターに渡すのに役立ち、2 つのパラメーターを渡すだけで済みます。パラメータの不一致はありません。


4.パラメーターの順序を調整するためのラッパーバインディングのバインド

次の Sub クラスのメンバー関数 sub の場合、最初のパラメーターは暗黙的な this ポインターです. オブジェクトを使用せずに Sub メンバー関数を呼び出したい場合は、サブ メンバー関数の最初のパラメーターをバインドできます。サブオブジェクトとして固定的に定義されています。例えば:

class Sub
{
     
     
public:
	int sub(int a, int b)
	{
     
     
		return a - b;
	}
};
int main()
{
     
     
	std::function<int(int, int)> func1 = std::bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
	cout << func1(1, 2) << endl;//-1
	return 0;
}
  • この時点で、引き算用に 2 つのパラメーターを渡すだけで済みます。これは、呼び出し時に匿名オブジェクトをこのポインターに渡すのに役立つためです。

次に、2 つの減算パラメーターの順序を交換したい場合は、バインド時に placeholders::_ 1 と placeholders::_ 2 の位置を交換するだけです。

class Sub
{
     
     
public:
	int sub(int a, int b)
	{
     
     
		return a - b;
	}
};
int main()
{
     
     
	//调整顺序
	std::function<int(int, int)> func2 = std::bind(&Sub::sub, Sub(), placeholders::_2, placeholders::_1);
	cout << func2(1, 2) << endl;//1
	return 0;
}

このとき、実パラメータ 1 は 2 番目のパラメータである _2 に、実パラメータ 2 は 1 番目のパラメータである _1 に渡され、2-1=1 となります。


5.バインドラッパーの意味

  • 関数の一部のパラメーターを固定値にバインドして、呼び出し時に一部のパラメーターを渡す必要がないようにします。
  • 関数パラメータの順序は柔軟に調整できます。

おすすめ

転載: blog.csdn.net/m0_64224788/article/details/130477090