%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
%%
# 可调用对象
可调用对象(callable object):可直接对其使用调用运算符`()` 的对象或表达式,C++中有以下五类:
- **函数**
- **函数指针**: 指向函数的指针
- **函数对象 / 仿函数**:即重载了调用运算符 `operator()` 的**类**;
- **`bind` 创建的函数对象**: 通过`std::bind` 创建的函数对象
- **`std::function<>` 实例对象** :可存储任何可调用对象,本身支持被直接调用。
- **lambda 表达式**(严格来说,属于一种函数对象)
> [!NOTE] **==类成员函数指针==本身不属于可调用对象**,不能直接调用,必须**通过一个实例对象**来进行调用
>
> 说明示例:
> ```cpp
> struct MyClass {
> void memfunc();
> };
>
> MyClass obj;
> // `func_ptr`为成员函数指针
> void (MyClass::*func_ptr)() = &MyClass::memfunc;
> // 需要通过对象来调用成员函数指针
> (obj.*funcptr)(); // 调用obj.memfunc;
> ```
>
> 同样原因,类成员函数指针也不能直接传给 `std::funtion` 包装,
> **必须通过 `std::bind()` 或者 `lambda` 为其绑定一个类实例**,先得到一个可调用对象,再赋给 `std::function`。
<br><br><br>
# 函数对象(仿函数)
**函数对象**:行为类似函数的"对象",也称**仿函数**(**Functor**)。
函数对象是由一个提供了 `operator()` 操作符的**类**实例化得到的对象,具有**类似函数的行为**。
函数对象的优点:
- 函数对象可拥有**成员变量**和**成员函数**,因此函数对象**可拥有状态(state)**。<br>在同一时间点,相同类型的两个不同的函数对象所表述的相同机能 (same functionality),可具备不同的状态。
- **每个函数对象有其自己的类型**。两个函数对象即使函数签名式相同,也可以有不同的类型。
```cpp
// 示例: 通过函数对象实现"为集合内每个元素增加一个指定值"
class AddValue {
private:
int value; // the value to add
public:
AddValue(int v) : value(v) {}
// 函数调用
void operator()(int &elem) const {
elem += value;
}
}
int main() {
list<int> coll = {1, 3, 4, 2, 5};
// add value 10 to each element:
for_each(coll.begin(), coll.end(), AddValue(10));
// add value 17 to each element:
for_each(coll.begin(), coll.end(), AddValue(17));
// add value 3 to each element:
for_each(coll.begin(), coll.end(), AddValue(3));
}
```
<br><br><br>
# function 对象
> 位于 `<functional>` 头文件中
C++11 提供了 `std::function<>` **类模版**,实例化得到的 **function 对象**可作为 C++中**任意可调用对象**的 **==包装器==**(Function Type Wrapper),
其可**存储、复制和调用** 指定类型的任意可调用对象。
## 用法说明
`std::function<T>` 的 **模版参数 `T`** 是其所能接收的**可调用对象**的 **==函数类型==**,
例如 `std::function<int(int, int)>` 表示一个接受两个 `int` 参数并返回一个 `int` 的可调用对象。
![[02-开发笔记/01-cpp/类型相关/cpp-指针相关#^03j3v1]]
## 使用示例
常见使用场景:
- 使用 `std::function<>` 对象**包装其它任意可调用对象**。
- 使用 `std::function<>` 对象包装 "**==成员函数指针==**",参见 [[02-开发笔记/01-cpp/类型相关/cpp-指针相关#方式一:使用function为成员函数指针直接生成一个可调用对象|cpp-指针相关#成员函数指针]]
- 将 `std::function<>` 模版类作为**模版参数**,使**STL 容器可存储相同函数类型的任意函数**。
- 某个函数返回一个 lamba 时,作为函数的返回类型
示例一:使用 funciont 对象包装可调用对象:函数、函数指针、函数对象、lambda、bind 对象
```cpp
#include <functional>
#include <iostream>
// 普通函数
int Add(int x, int y) { return x + y; }
// 函数对象; 仿函数
struct Adder {
int operator() (int x, int y) const {
return x + y;
}
};
// 类,用于演示成员函数指针
class MyClass {
public:
void memberFunc(int x, int y) {
return x + y;
}
};
int main() {
// 声明一个返回值为int, 接受两个int型参数的可调用对象类型,得到一个模版类。
std::function<int(int, int)> Func;
// 可以被赋予普通函数
Func = Add;
// 可以被赋予lambda
Func = [](int x, int y) { return x+y; };
// 可以被赋予一个函数对象;
Func = Adder();
// 可以被赋予函数指针
int (*fp)(int, int) = Add;
Func = fp;
// 可以被赋予bind创建的可调用对象
Func = std::bind(Add, 5, std::placeholders::_1);
// 包装"类成员函数指针", 从而可供任何该类的实例调用 (第一参数需要声明为对该类实例的引用)
std::function<void(const MyClass&, int, int)> MemFunc;
MemFunc = &MyClass::memberFunc;
MyClass obj;
MemFunc(obj, 42, 77);
}
```
示例二:声明 vector 中的元素类型为 `std::function<>` 类型,从而**存储可调用对象**。
```cpp
void func (int x, int y);
// 声明vector中的元素类型为function<void(int,int)>类型
vector<function<void(int,int)>> tasks;
// 向其中添加"函数类型"相同的不同函数
tasks.push_back(func);
tasks.push_back([](int x, int y) { ... });
// 调用各个函数
for (std::function<void(int, int) f : tasks) {
f(69, 69);
}
```
示例三:作为函数返回类型,接收返回的 lambda 表达式
```cpp
function<int(int, int)> returnLambda() {
return [](int x, int y) -> int { return x + y;};
}
int main() {
auto lf = returnLambda();
lf(6, 7);
}
```
<br><br>
## function 的性能
`std::function` 会比简单的函数指针或直接的函数调用**有==更多开销==**。
- function 类型实例本身的大小通常是**16~32 个字节**(取决于编译器具体实现和平台),通常包括:
- 一个**指向其可调用对象的指针**
- 一个或多个函数指针,用于管理如何调用、复制和销毁其内部的可调用对象。
- 一个小的缓冲区,用于存储小的可调用对象。
- function 通常使用 **动态内存** 来 **存储** 其可调用对象,尤其是当这些对象超过某个大小阈值时。
- 在大多数实现上,也提供了一个称为 "small object optimization" 或 "small buffer optimization (**SBO**)" 的特性,即**对于小的可调用对象**(如小的 lambda 表达式或函数指针),function 类将直接在内部存储,而不会申请动态内存。
可通过 `sizeof` 查看 `std::function` 在当前平台和编译器上的确切大小:
```c++
#include <iostream>
#include <functional>
int main() {
std::cout << "Size of std::functional<void()>: " <<
sizeof(std::function<void()>) << " bytes" << std::endl;
}
// 我的机器上是32字节.
```
因此,**function 包装不适用于作为递归函数**,性能会非常低!
<br><br><br>
# 函数参数绑定—bind 函数
> `bind` 函数以及 `placeholder` 命名空间位于头文件 `<functional>` 中
`bind` 函数用于 "**==绑定==一个或多个参数**" 到一个可调用对象,**==生成一个新的可调用对象==**,**适配原对象的参数列表**[^1] 。
## 用法说明
```cpp
auto newCallable = std::bind(callable, arg_list);
```
`bind` 函数返回一个可调用对象 `newCallable`,**当调用 `newCallable` 时,等价于使用参数列表 `arg_list` 来调用`callable`** 。
**`arg_list` 对应于 `callable` 参数列表**,其中可指定**两种类型的参数**:
- (1)**==绑定==到 `newCallable` 的参数**:直接给出,参数默认以 "**==拷贝/副本==**" 的方式进行绑定。
- 若要以 "**==引用==**" 或 "**==const 引用==**" 形式传递,需要使用**标准库函数 `std::ref()` 与 `std::cref()`** 包裹。
- (2)**==调用== `newCallable` 时需要传递的参数**:以 "**==占位符==**" 形式声明:`std::placeholders::_n`;
- 数值 `n` 表示**传递给 `newCallable` 的参数位置**, `_1` 代表传递给 `newCallable` 的第一个参数,`_2` 代表传递的第二个参数,以此类推。
> [!note] 关于 bind 生成的可调用对象中的 "绑定的参数" 与 "接收的参数" [^2]
>
> - bind 对象 **==存储==着传递给 `std::bind` 的 ==被绑定的实参== 的"副本"**(默认按值传递时)
> - bind 对象 **==接收==的所有实参** 都是通过 "**==引用传递==**"
> - (因为 **bind 对象的 `operator()` 是以 "完美转发" 实现,故形参为 `T&&` 转发引用类型**)
>
![[02-开发笔记/01-cpp/类型相关/cpp-指针相关#^03j3v1]]
#### 绑定到 "类的非静态成员函数" 时的用法
> [!NOTE]
>
> 当`callable` 是 **指向==非静态成员函数==的指针** 时, `arg_list` 中首项参数必须是一个指向 **实例对象的引用或指针**(包括智能指针,如 `std::shared_ptr` 和 `std::unique_ptr`),剩余参数则对应该成员函数的参数列表。
>
> ```cpp
> MyClass Obj;
> auto func = std::bind(&MyClass::mem_func, &obj, param1, param2);
> ```
>
<br><br>
## 使用示例
使用场景:参数的重新排列、固定化(固定某些参数, 而让调用时可传递的参数减少)以及柯里化。
示例一:**参数固定化**
```cpp
int add(int a, int b) { return a + b; }
// 通过bind生成一个可调用对象add_five, 固定其调用add时首个参数为5, 第二个参数为传递给其的第一个参数;
auto add_five = bind(add, 5, std::placeholders::_1);
int result = add_five(3); // result为8 (5+3)
```
示例二:**参数重新排序**
```cpp
int divide(int a, int b) { return a / b; }
auto inv_divide = std::bind(divide, std::placeholders::_2, std::placeholders::_1);
int result = inv_divide(2, 10); // result为5(10/2)
```
示例三:以 "**引用**" 的方式绑定参数:`ref`,`cref`
```cpp
void increment(int &x) { ++x; }
int add(int a, int b) { return a + b; }
int main(void) {
int value = 0;
auto bi_incre = std::bind(increment, std::ref(value));// 通过ref()以"引用"的形式绑定参数;
bi_incre(); // 调用后, value的值变为1;
// std::cref 用于创建一个类型为 std::reference_wrapper<const T> 的对象
// 其作用是保持对给定对象的常量引用。
int value2 = 55;
auto add_by_value = std::bind(add, std::cref(value), std::placeholders::_1);
int res = add_by_value(3); // res为58 (55+3);
value2 = 11;
res = add_by_value(6); // res为17 (11+6);
}
```
示例四:**绑定成员函数**
```cpp
struct Multiplier {
int factor;
int multiply(int x) {
return x * factor;
}
};
// 绑定成员函数
// std::bind()的第一项参数接受成员函数指针
// 第二项参数接受一个实例对象的引用或指针, 剩余参数为成员函数的参数列表.
Multiplier multiplier{5};
auto bound_multiply =
std::bind(&Multiplier::multiply, &multiplier, std::placeholders::_1);
int result = bound_multiply(3); // result 为 15 (3 * 5)
```
示例五:**为各种可调用对象绑定参数**
```cpp
void funct (int x, int y);
auto l = [](int x, int y) { ... };
class C {
public:
void operator() (int x, int y) const;
void memfunc(int x, int y) const;
};
int main() {
C c;
std::shared_ptr<C> sp = std::make_shared<C>();
// bind() uses callable objects to bind arguments:
std::bind(func, 77, 33)(); // call: func(77,33)
std::bind(l, 77, 33)(); // call: l(77,33)
std::bind(C(), 77, 33)(); // call: C::operator()(77,33)
std::bind(&C::memfunc, c, 77, 33)(); // call: c.memfunc(77,33)
std::bind(&C::memfunc, sp, 77, 33)(); // call: sp->memfunc(77,33)
}
```
<br><br><br>
# Reference Wrapper 引用包装器
> 位于 `<functional>` 头文件中
C++11 中引入的 `std::reference_wrapper<>` 类模版,用作为 **引用包装器**。
对于给定类型 `T`,`std::reference_wrapper<T>` 类提供了:
- `std::ref()` 用以隐式转换为 `T&`
- `std::cref()` 用以隐式转换为 `const T&`
上述两个函数将返回**一个`std::reference_wrapper<T>` 实例对象**。
要得到被引用的原对象,可通过该对象的 **`.get()` 方法**获取,**返回一个被包装对象的引用**。
### 使用示例
使用场景:
1. 用于为以`by value`方式接受参数的**函数模版**提供 reference 类型,即让**函数模版能够接受"引用"类型**,而无需进行模版特化。
- 该这个特性被 C++标准库运用于各个地方,例如,基于该特性可实现:
- `make_pair()` 创建一个` pair<> of references`
- `make_tuple()` 能够创建一个 `<tuple> of references`
- `Binder` 能够绑定 `(bind) reference`
- `Thread` 能够以 `by reference` 形式传递实参
2. 作为**对象包装器**,用以**在 STL 容器中存储引用**。
> [!caution] STL 标准容器(如 `std::vector`,`std::list` 等)**不能"直接"存储引用类型**。
>
> 因为**引用不是对象**,**没有实际的存储大小**,而是别名或已存在对象的另一个名称。试图创建例如`std::vector<int&>`的容器会导致编译错误。
使用示例一:
```cpp
template <typename T>
void foo(T val); // 模版参数推导中, 对于实参int&, 推导结果为T是int类型, 会忽略引用.
// 对于上述函数模版, 可以直接如下传递引用参数:
int x;
foo(std::ref(x)); // T变成int&
foo(std::cref(x)); // T变成const int&
```
使用示例二:
```cpp
// 通过reference_wrapper声明容器的元素类型为引用
// 搭配ref()或cref()向容器中传入对象的引用.
// vector<int&> vec; // Error
vector<reference_wrapper<int>> vec; // OK
int val = 3;
// ref()返回一个std::reference_wrapper<int>实例对象, 包装了被引用的原对象
vec.push_back(ref(val));
// vec中的引用也将看到更改
val = 4;
cout << vec[0].get() << endl; // 通过.get()获取一个被包装对象的引用
cout << static_cast<int&>(vec[0]) << endl; // 与上语句等价
```
<br><br><br>
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
# ♾️参考资料
# Footnotes
[^1]: 《C++ Primer》(P354)
[^2]: 《Effective Morder C++》Item34