函数模板

std::function<> 模板到底是什么? 可以简单理解为对应 C 语言中的函数指针。入参或绑定的参数如果是原始指针或引用时,需要特别注意其生存周期。

关于函数类模板,学习《Effective.Modern.C++》P39

And maybe now you’re thinking “What’s a std::function object?” So let’s clear that up

boost::empty_value 的意义?

待补充

获得 std::function 对象

Lambda expressions

Constructs a closure: an unnamed function object capable of capturing variables in scope. 摘自 lambda

Function objects

A function object is any object for which the function call operator is defined. 摘自 functional

std::function,推荐查看其 Example

Class template std::function is a general-purpose polymorphic function wrapper. Instances of std::function can store, copy, and invoke any CopyConstructible Callable target – functions, lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to data members. 摘自 function

需要强调的是 C++ 标准未对 std::bind 表达式的返回类型做出定义,其返回值并不对应我们熟悉的任何类型,但可以直接赋值给 std::function<>

1
2
3
4
5
6
7
8
9
10
std::function<int(int)> func = std::tolower;
std::function<int(int)> lamb = [](int a) { return a; };
std::string str("niel");
std::function<size_t(void)> bind = std::bind(&std::string::size, &str);
std::function<int(int, int)> object = std::plus<int>();
std::function<size_t(const std::string&)> p2mf = &std::string::size;

//std::function<size_t(void)> bind = std::bind(&std::string::size, std::ref(str));
//// 拷贝传参,请务必明白在做什么。also works, but str is copied
//std::function<size_t(void)> bind = std::bind(&std::string::size, str);

模板参数

1
2
3
// ERR,末尾多余的 const 造成报错内容在千里之外
using SENDFUNC = std::function<void(CBD , void const * const , size_t ) const>;
using SENDFUNC = std::function<void(CBD , void const * const , size_t ) /*const*/>;

递归调用

为什么 function<> 对象递归调用自身不能使用 auto?

vc2015 IntelliSence 提示

使用 auto 类型说明符声明的变量不能出现在其自身的初始值设定项中

1
2
3
4
5
6
7
8
9
10
11
// auto sum2 = [sum2](int a) {  // 编译错误 why?
// std::function<int(int)> sum2 = [sum2](int a) { // 执行错误 why? 参考拷贝构造函数的参数为什么要使用引用类型。
std::function<int(int)> sum2 = [&sum2](int a) {
if (a>0) {
return (a + sum2(a - 1));
}
else {
return 0;
}
};
spdlog::info("T({})={}", 30, sum2(30));

虽然 &sum2 引用能够调用成功,但我们必须时刻关注 sum2 对象的生存周期。比如以下代码就会崩溃

1
2
3
4
5
6
std::function<int(int)> sum2Copy;
{
sum2 = ...
sum2Copy = sum2;
}
spdlog::info("T({})={}", 30, sum2Copy(30));

对比虚函数

存在神话面向对象,过度使用继承的一个时代,一批人。他们使用基础类型指针,通过虚函数约定回调格式。

上述做法的限制:

  • 局限于继承回调类,重写唯一的虚函数,当类中存在多种请求(大多情形如此)时,在同一回调函数中做分支处理(缺点),分支处理依赖 CBD 枚举类型,以此判断应该使用哪种数据交换类解析 void* 指针

改用 std::function 类模板带来的好处:

  1. 将上述混在一起的分支抽离到不同函数中处理,取消通过 CBD 类型选择解析类型的过程;
  2. 丢弃 void* 指针,在「调用签名」中使用强类型 指针 可以避免 void* 指针向具体数据交换类性指针的转换;
  3. 强类型指针,通过使用智能指针避免手动管理内存,丢弃原生指针;智能指针 的常量引用,不要拷贝)实际业务分析,事实上应该使用顺序容器,vector 即可
  4. 很大程度上减少 CBD 枚举类型的存在感,甚至丢弃;
  5. 数据交换类(Dyna/Tick/stNews 等)取消 强类型 成员变量,对外只提供接口。进一步降低文件之间的依赖关系。

上述优点的核心就是:编码的时候,展现层最低程度地依赖网络层,尤其是「约定」什么的最烂了。

无法判断相等

两个 std::function 无法做比较:大于、小于和相等。

只能判断某个 std::function 是否包含目标,和 nullptr 的相等比较已经在 C++20 中移除。

传值还是引用

传引用潜在威胁严重:参考 https://github.com/tnie/YD-function/tree/check_ref 分支

As with any other type, it’s safe to store a reference only if you have some other way to guarantee that the referred object is still valid whenever that reference is used. 摘抄来源

“重载了函数运算符的类”的对象,调用由此赋值产生的 function<> 对象时,操作的是前者的拷贝,而非本身。——此问题源于构造 function<> 对象,而非 function<> 对象传参。正确做法是:

1
2
3
4
auto multt3 = Multt3();
//f_multiN f3 = multt3; // 复制
//f_multiN& f3 = multt3; // err
f_multiN f3 = std::ref(multt3); // 还真的可以

结合 stackoverflow 网站多个提问,发现还是以传值为主,基本没有传引用的场景。但我还是用了引用,而且是常量引用!

仔细查看 std::function 的手册,会发现 std::function::operator() 函数是带 const 修饰的,这也意味着使用 const std::function<int(int)>& 传常量引用是完全可以的,无需担心修改源对象非法(指通过 std::bind 绑定的对象或者重载了函数调用运算符的对象),置于 Why is the std::function () operator const? 抽空了解一下,对此感到困惑还是源于一直未弄懂 function 类模板 到底是对象呢,还是指针?

回调时的合法性

使用“重载了函数调用符号”的类、std::bind(&Class::Function, ObjectPointer, std::placeholder::_1) 等涉及类的可调用类型时,因为跨内存模块(动态库)、跨线程,当对象析构后执行回调如何处理?

通俗地讲,在调用类的普通成员函数时如何判断所属对象已经析构,不然当成员函数使用成员变量时直接造成程序崩溃。

  1. 析构函数遇上多线程怎么办?好难,┭┮﹏┭┮,请尽量阅读 本文 PDF 版
  2. 由上述核心引起的 std::function 遇上多线程

网络层是否不应该负责主要是做不到)对展现层传入的类模板 std::function<> 对象做深度的有效性判断?毕竟展现层传入的 raw 究竟是函数指针、lambda 表达式还是 std::bind() 返回值,数据层都一无所知

网络层也就只能检查 std::function<> 对象

是否存储可调用函数对象 c

至于对象 c 的有效性——比如通过 std::bind 绑定对象 t 时,t 是否析构——网络层是无法检查的

所以,展现层应该自检。可能的一种实现方式是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*网络层接口*****************************/
using YDDATA2CALLBACK1 = std::function<void (QID qid, CBD cbd, int period, const shared_ptr<Dyna> data)>;

QID YDdata_subscribeDynaWithOrder(const char *code, YDDATA2CALLBACK1 cb, const char* order /*= nullptr*/, bool desc /*= false*/) ;

/*展现层可能的实现方式*************************************/
class EXAMPLE : public std::enable_shared_from_this<EXAMPLE>
{
public:
std::shared_ptr<EXAMPLE> getptr() {
return shared_from_this();
}

void callback1(QID qid, CBD cbd, int period, const std::shared_ptr<Dyna> data, std::weak_ptr<EXAMPLE> ptr)
{
if (ptr.lock() == nullptr)
return;
// code
}

QID subscribeDyna()
{
std::weak_ptr<EXAMPLE> wptr = shared_from_this();
return YDdata_subscribeDynaWithOrder("SH000001", std::bind(&EXAMPLE::callback1, this, _1, _2, _3, _4, wptr));
}
};

using Good = EXAMPLE;
// 只允许在先前已被std::shared_ptr 管理的对象上调用 shared_from_this 。否则调用行为未定义
int main()
{
// 正确的示例
std::shared_ptr<Good> gp1 = std::make_shared<Good>();
std::shared_ptr<Good> gp2 = gp1->getptr();

// 错误的使用示例:调用 shared_from_this 但其没有被 std::shared_ptr 占有
try {
Good not_so_good;
std::shared_ptr<Good> gp1 = not_so_good.getptr();
}
catch (std::bad_weak_ptr& e) {
// C++17 前为未定义行为; C++17 起抛出 std::bad_weak_ptr 异常
std::cout << e.what() << '\n';
}
return 0;
}

上述 EXAMPLE 类,因为回调时对象可能已经析构,所以回调函数 callback1() 使用类的成员并不合适(虽然使用 wptr 判断了),且 bind()this, wptr 也有重复( wptr 主要是为了判断对象是否还活着, this 无现实意义)。改进 EXAMPLE 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*展现层可能的实现方式*************************************/
class EXAMPLE : public std::enable_shared_from_this<EXAMPLE>
{
public:
static void callback1(QID qid, CBD cbd, int period, const std::shared_ptr<Dyna> data, std::weak_ptr<EXAMPLE> wptr)
{
auto ptr = wptr.lock();
if (ptr == nullptr)
return;
ptr->_callback1(qid, cbd, period, data);
}

QID subscribeDyna()
{
std::weak_ptr<EXAMPLE> wptr = shared_from_this();
return YDdata_subscribeDynaWithOrder("SH000001", std::bind(&EXAMPLE::callback1, _1, _2, _3, _4, wptr));
}

private:
void _callback1(QID qid, CBD cbd, int period, const std::shared_ptr<Dyna> data)
{
// code
}
};