上一篇:《深入理解C++11》笔记-Unicode编码支持和原生字符串字面量
接下来的内容将会补充在《深入理解C++11》书中没有涉及的一些比较常用的特性,内容取自《深入应用C++11》。
在C++中,存在“可调用对象(Callable Objects)”这么一个概念。准确来说,可调用对象有如下几种定义:
- 是一个函数指针。
- 是一个具有operator()成员函数的类对象(仿函数)。
- 是一个可被转换为函数指针的类对象。
- 是一个类成员(函数)指针。
void func(void)
{
}
struct Foo
{
void operator()(void)
{
}
};
struct Bar
{
using fr_t = void(*)(void);
static void func(void)
{
}
operator fr_t(void)
{
return func;
}
};
struct A
{
int a_;
void mem_func(void)
{
}
};
int main(void)
{
void(*func_ptr)(void) = &func; // 1. 函数指针
func_ptr();
Foo foo; // 2. 仿函数
foo();
Bar bar; // 3. 可被转换为函数指针的类对象
bar();
void (A::*mem_func_ptr)(void) // 4. 类成员函数指针
= &A::mem_func;
int A::*mem_obj_ptr // 或者是类成员指针
= &A::a_;
A aa;
(aa.*mem_func_ptr)();
aa.*mem_obj_ptr = 123;
return 0;
}
上面对可调用类型的定义里并没有包括函数类型或者函数引用(只有函数指针)。这是因为函数类型并不能直接用来定义对象;而函数引用从某种意义上来说,可以看做一个const的函数指针。C++中的可调用对象虽然具有比较统一的操作形式(除了类成员指针之外,都是后面加括号进行调用),但定义方法五花八门。这样在我们试图使用统一的方式保存,或传递一个可调用对象时,会十分烦琐。现在,C++11通过提供std::function和std::bind统一了可调用对象的各种操作。
std::function
std::function是可调用对象的包装器。它是一个类模板,可以容纳除了类成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们
#include <iostream>
#include <functional>
void func(void)
{
std::cout << __FUNCTION__ << std::endl;
}
class Foo
{
public:
static int foo_func(int a)
{
std::cout << __FUNCTION__ << "(" << a << ") ->: ";
return a;
}
};
class Bar
{
public:
int operator()(int a)
{
std::cout << __FUNCTION__ << "(" << a << ") ->: ";
return a;
}
};
int main(void)
{
std::function<void(void)> fr1 = func; // 绑定一个普通函数
fr1(); // func
// 绑定一个类的静态成员函数
std::function<int(int)> fr2 = Foo::foo_func;
std::cout << fr2(123) << std::endl; // foo_func(123) ->: 123
Bar bar;
fr2 = bar; // 绑定一个仿函数
std::cout << fr2(123) << std::endl; // operator()(123) ->: 123
return 0;
}
从上面我们可以看到std::function的使用方法,当我们给std::function填入合适的函数签名(即一个函数类型,只需要包括返回值和参数表)之后,它就变成了一个可以容纳所有这一类调用方式的“函数包装器”。
另外,std::function还能作为函数的入参以及函数指针:
class A
{
std::function<void()> callback_; // 函数指针
public:
A(const std::function<void()>& f) // 函数入参
: callback_(f)
{}
void notify(void)
{
callback_(); // 回调到上层
}
};
class Foo
{
public:
void operator()(void)
{
std::cout << __FUNCTION__ << std::endl;
}
};
int main(void)
{
Foo foo;
A aa(foo);
aa.notify();
return 0;
}
并且使用std::function有个好处就是能够将对象的成员函数作为参数,如果使用自定义的函数指针则会报错:
using pfunc = void(*)();
class A
{
pfunc callback_;
public:
A(pfunc f)
: callback_(f)
{}
void notify(void)
{
callback_(); // 回调到上层
}
};
class Foo
{
public:
void operator()(void)
{
std::cout << __FUNCTION__ << std::endl;
}
};
int main(void)
{
Foo foo;
A aa(foo); // 编译失败
aa.notify();
return 0;
}
std::bind
std::bind用来将可调用对象与其参数一起进行绑定。绑定后的结果可以使用std::function进行保存,并延迟调用到任何我们需要的时候。它主要有两大作用:
- 将可调用对象与其参数一起绑定成一个仿函数。
- 将多元(参数个数为n,n>1)可调用对象转成一元或者(n-1)元可调用对象,即只绑定部分参数。
void call_when_even(int x, const std::function<void(int)>& f)
{
if (x % 2 == 0)
{
f(x);
}
}
void output(int x)
{
std::cout << x << " ";
}
void output_add_2(int x)
{
std::cout << x + 2 << " ";
}
int main(void)
{
auto func= std::bind(output, std::placeholders::_1);
for (int i = 0; i < 10; ++i)
{
call_when_even(i, func);
}
std::cout << std::endl; // 0 2 4 6 8
func= std::bind(output_add_2, std::placeholders::_1);
for (int i = 0; i < 10; ++i)
{
call_when_even(i, func);
}
std::cout << std::endl;
return 0; // 2 4 6 8 10
}
同样还是上面std::function中最后的一个例子,只是在这里,我们使用了std::bind,在函数外部通过绑定不同的函数,控制了最后的执行结果。我们使用auto类型保存std::bind的返回结果,是因为我们并不关心std::bind真正的返回类型(实际上std::bind的返回类型是一个stl内部定义的仿函数类型),只需要知道它是一个仿函数,可以直接赋值给一个std::function。当然,这里直接使用std::function类型来保存std::bind的返回值也是可以的。std::placeholders::_1是一个占位符,代表这个位置将在函数调用时,被传入的第一个参数所替代。可以通过以下的例子观察怎么使用占位符:
std::bind(output, 1, 2)(); // 1 2
std::bind(output, std::placeholders::_1, 2)(1); // 1 2
std::bind(output, 2, std::placeholders::_1)(1); // 2 1
std::bind(output, 2, std::placeholders::_2)(1); // 编译失败,调用时没有第二个参数
std::bind(output, 2, std::placeholders::_2)(1, 2); // 2 2
std::bind(output, std::placeholders::_1, std::placeholders::_2)(1, 2); // 1 2
std::bind(output, std::placeholders::_2, std::placeholders::_1)(1, 2); // 2 1
bind还有一个强大之处就是可以组合多个函数。假设要找出集合中大于5小于10的元素个数应该怎么做呢?
int main(void)
{
std::vector<int> arr = { 0,1,2,3,4,5,6,7,8,9 };
auto f = std::bind(std::logical_and<bool>(),
std::bind(std::greater<int>(), std::placeholders::_1, 5),
std::bind(std::less_equal<int>(), std::placeholders::_1, 10));
int count = std::count_if(arr.begin(), arr.end(), f);
std::cout << count << std::endl;
return 0;
}
以上代码中,std::logical_and<bool>()
是一个模板结构体成员函数,作用是返回传入的两个参数的逻辑与结果,例如:
std::logical_and<bool>()(1, 2); // 返回1&&2的结果
std::logical_and<bool>()
通过bind绑定了std::greater<int>()
和std::less_equal<int>()
,就能得到他们两个返回值的逻辑与结果。而std::greater<int>()
和std::less_equal<int>()
也是模板结构体成员函数,第一个参数是被比较值,绑定了第一个入参,第二参数是比较值,通过两个参数比较返回bool值。接着讲bind返回的函数,传入std::count_if,它的作用是遍历指定的一组数据并传入指定函数,函数返回true则count+1。这样就能得到集合中大于5小于10的元素个数了。
以上例子只是为了说明bind组合函数的方法,实际上用lambda函数实现更为简单:
int main(void)
{
std::vector<int> arr = { 0,1,2,3,4,5,6,7,8,9 };
int count = std::count_if(arr.begin(), arr.end(), [](int x) {
return x > 5 && x < 10;
});
std::cout << count << std::endl;
return 0;
}