std::function 介绍
C++ 中有很多表示可调用代码段的方式,如函数指针、可调用类以及 lambda 表达式等,为了可以统一传递这些对象,我们可以通过函数模板的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void print(int i) { std::cout << i << std::endl; }
template<typename Func> void doWithFunc(int n, Func f) { for (int i=0; i<n; ++i) { f(i); } }
int main() { doWithFunc(10, print); doWithFunc(10, [](int i) { std::cout << i << std::endl; }); doWithFunc(10, &print); }
|
这样要求将函数实现成模板函数,比较麻烦。一个更好的替代方式是使用 C++ 标准库中的 std::function 对可调用对象进行包装,这样能够容易的做到统一。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void print(int i) { std::cout << i << std::endl; }
void doWithFunc(int n, std::function<void(int)> f) { for (int i=0; i<n; ++i) { f(i); } }
int main() { doWithFunc(10, print); doWithFunc(10, [](int i) { std::cout << i << std::endl; }); doWithFunc(10, &print); }
|
std::function 对象也是一个可调用对象,支持以下操作:
- 可调用,也就是重载了
()
运算符。
- 支持拷贝、移动以及赋值操作,这会将其中保存的可调用对象进行相应操作。
- 可以通过其它函数进行初始化或赋值,只要相应的函数签名能够匹配。
- 有一个合法的
null
状态,也就是没有函数和该对象绑定。
std::function 是 C++ 11标准中引入的,统一了可调用对象,使得回调函数实现等变得十分便利。具体用法十分简单,这里主要针对其实现原理进行介绍。
std::function 实现原理
FunctionPtr 设计
这里实现一个基本的 FunctionPtr
类,使用方法和 std::function 类一样,但是是一个简易的实现版本,目的在于将基本的原理描述清楚。
我们知道 FunctionPtr
是可调用对象的一个包装,换句话说,对于函数需要保存其函数指针,对于可调用类需要保存其一个实例,对于不同的对象需要保存不同的实例,这些实例都需要支持operator()
接口,因此我们可以通过继承和多态来进行设计。FunctionPtr
类持有一个FunctorBridge
类的指针:
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 47 48 49 50 51 52 53
| template <typename Signature> class FunctionPtr;
template <typename R, typename... Args> class FunctionPtr<R(Args...)> { private: FunctorBridge<R, Args...>* bridge;
public: FunctionPtr() : bridge(nullptr) {} FunctionPtr(const FunctionPtr& other);
FunctionPtr(FunctionPtr& other) : FunctionPtr(static_cast<const FunctionPtr&>(other)) {}
FunctionPtr(FunctionPtr&& other) : bridge(other.bridge) { other.bridge = nullptr; } template <typename F> FunctionPtr(F&& f);
FunctionPtr& operator=(const FunctionPtr& other) { FunctionPtr tmp(other); swap(*this, tmp); return *this; }
FunctionPtr& operator=(FunctionPtr&& other) { delete bridge; bridge = other.bridge; other.bridge = nullptr; return *this; }
template <typename F> FunctionPtr& operator=(F&& f) { FunctionPtr tmp(std::forward<F>(f)); swap(*this, tmp); return *this; }
~FunctionPtr() { delete bridge; }
friend void swap(FunctionPtr& fp1, FunctionPtr& fp2) { std::swap(fp1.bridge, fp2.bridge); } R operator()(Args... args) const; };
|
这里将一些基本的框架进行了编写,包括拷贝、赋值以及移动相关的操作。这里留了 3 个最重要的函数没有实现,分别是:
- 拷贝构造函数
FunctionPtr(const FunctionPtr& other);
的具体实现,这里涉及到 bridge 指向对象的拷贝,因此我们后面介绍。
- 通过可调用对象进行初始化的构造函数的实现,这也是最重要的一个函数:
1 2
| template <typename F> FunctionPtr(F&& f);
|
R operator()(Args... args) const;
的实现,这也涉及到具体 FunctorBridge
类的原理。
FunctionBridge 继承结构
FunctionBridge
是为了实现多态的基类,规定了一些接口:
1 2 3 4 5 6 7
| template <typename T, typename... Args> class FunctorBridge { public: virtual ~FunctorBridge() = default; virtual FunctorBridge* clone() const = 0; virtual T invoke(Args... args) const = 0; };
|
其中 clone()
能够创建一个自身的拷贝,用于 FunctionPtr
拷贝构造函数实现深拷贝。invoke
通过模板参数进行具体的可调用对象的调用。
具体的实现子类被设计成一个模板类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| template <typename Functor, typename R, typename... Args> class SpecificFunctorBirdge : public FunctorBridge<R, Args...> { private: Functor functor;
public: template <typename FunctorFwd> SpecificFunctorBirdge(FunctorFwd&& functor) : functor(std::forward<FunctorFwd>(functor)) {}
virtual SpecificFunctorBirdge* clone() const override { return new SpecificFunctorBirdge(functor); }
virtual R invoke(Args... args) const override { return functor(std::forward<Args>(args)...); } };
|
这个模板类除了可调用对象返回类型R
和参数Args...
两个模板参数外,还增加了一个额外的模板参数Functor
,表示对于不同可调用对象实际保存的类型。例如,对于函数对象,这里 Functor
就会是相应的函数指针,对于可调用类,Functor
就会是相应的类名,对于 lambda 表达时,Functor
就是相应匿名类的类型。
这个具体类很简单,保存了一个 Functor
类型的实例,构造函数通过完美转发调用Functor
类相应的构造函数进行构造。clone()
也就是创建了一个含有 functor
实例拷贝的对象,调用操作也就是简单的将参数进行完美转发调用。
最后再来补齐 FunctionPtr
类中 3 个遗留的最重要的成员函数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| template <typename R, typename... Args> FunctionPtr<R(Args...)>::FunctionPtr(const FunctionPtr& other) : bridge(nullptr) { if (other.bridge) { bridge = other.bridge->clone(); } }
template <typename R, typename... Args> R FunctionPtr<R(Args...)>::operator()(Args... args) const { return bridge->invoke(std::forward<Args>(args)...); }
template <typename R, typename... Args> template <typename F> FunctionPtr<R(Args...)>::FunctionPtr(F&& f) : bridge(nullptr) { using Functor = std::decay_t<F>; bridge = new SpecificFunctorBirdge<Functor, R, Args...>(std::forward<F>(f)); }
|
编写简单的测试代码进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void print(int i) { std::cout << i << std::endl; }
void doWithFunc(int n, FunctionPtr<void(int)> f) { for (int i=0; i<n; ++i) { f(i); } }
int main() { doWithFunc(10, print); doWithFunc(10, [](int i) { std::cout << i << std::endl; }); doWithFunc(10, &print); }
|
结果和标准库 std::function 一样,说明我们设计的 FunctionPtr 类具备相应的功能。
结论
这里实现的 FunctionPtr
类只是具备了 std::function
最核心的功能,也说明清楚了对于 C++ 中不同的可调用对象是如何进行保存的,也就是保存其相应的退化后的类型的实例。需要注意的是关于判断两个 FunctionPtr
类型保存的可调用对象是否一样还没有进行实现(也就是实现operator==
和operator!=
),在《C++ Templates》中有具体的实现。