22 桥接静态和动态多态
本章实现了FunctionPtr<>
,类似于std::function
。
22.1 从函数对象到函数指针再到std::function
下面是一个可以接收任意函数对象并调用的模板:
// bridge/forupto1.cpp
#include <vector>
#include <iostream>
template<typename F>
void forUpTo(int n, F f)
{
for (int i = 0; i != n; ++i)
{
f(i); // call passed function f for i
}
}
void printInt(int i)
{
std::cout << i << ' ';
}
int main()
{
std::vector<int> values;
// insert values from 0 to 4:
forUpTo(5,
[&values](int i) {
values.push_back(i);
});
// print elements:
forUpTo(5,
printInt); // prints 0 1 2 3 4
std::cout << '\n';
}
基本每一次调用forUpto()
都会实例化新的函数,这会增加代码体积,解决方法是不实现为模板而是通过函数指针:
// bridge/forupto2.hpp
void forUpTo(int n, void (*f)(int))
{
for (int i = 0; i != n; ++i)
{
f(i); // call passed function f for i
}
}
但是这就不能传递lambda对象了(因为是类),为此可以使用std::function
:
// bridge/forupto3.hpp
#include <functional>
void forUpTo(int n, std::function<void(int)> f)
{
for (int i = 0; i != n; ++i)
{
f(i); // call passed function f for i
}
}
22.2 泛化的函数指针
本章接下来实现一个可以替代std::funtion
的FuntionPtr<>
:
// bridge/forupto4.cpp
#include "functionptr.hpp"
#include <vector>
#include <iostream>
void forUpTo(int n, FunctionPtr<void(int)> f)
{
for (int i = 0; i != n; ++i)
{
f(i); // call passed function f for i
}
}
void printInt(int i)
{
std::cout << i << ' ';
}
int main()
{
std::vector<int> values;
// insert values from 0 to 4:
forUpTo(5,
[&values](int i) {
values.push_back(i);
});
// print elements:
forUpTo(5,
printInt); // prints 0 1 2 3 4
std::cout << '\n';
}
FuntionPtr<>
的特性包括:
- 调用保存任意的可调用对象
- 可以拷贝、移动和赋值
- 可以为空
根据以上需求,实现的FunctionPtr<>
如下:
// bridge/functionptr.hpp
// primary template:
template<typename Signature>
class FunctionPtr;
// partial specialization:
template<typename R, typename... Args>
class FunctionPtr<R(Args...)>
{
private:
FunctorBridge<R, Args...>* bridge;
public:
// constructors:
FunctionPtr() : bridge(nullptr) {
}
FunctionPtr(FunctionPtr const& other); // see functionptr-cpinv.hpp
FunctionPtr(FunctionPtr& other)
: FunctionPtr(static_cast<FunctionPtr const&>(other)) {
}
FunctionPtr(FunctionPtr&& other) : bridge(other.bridge) {
other.bridge = nullptr;
}
// construction from arbitrary function objects:
template<typename F> FunctionPtr(F&& f); // see functionptr-init.hpp
// assignment operators:
FunctionPtr& operator=(FunctionPtr const& other) {
FunctionPtr tmp(other);
swap(*this, tmp);
return *this;
}
FunctionPtr& operator=(FunctionPtr&& other) {
delete bridge;
bridge = other.bridge;
other.bridge = nullptr;
return *this;
}
// construction and assignment from arbitrary function objects:
template<typename F> FunctionPtr& operator=(F&& f) {
FunctionPtr tmp(std::forward<F>(f));
swap(*this, tmp);522 Chapter 22: Bridging Static and Dynamic Polymorphism
return *this;
}
// destructor:
~FunctionPtr() {
delete bridge;
}
friend void swap(FunctionPtr& fp1, FunctionPtr& fp2) {
std::swap(fp1.bridge, fp2.bridge);
}
explicit operator bool() const {
return bridge == nullptr;
}
// invocation:
R operator()(Args... args) const; // see functionptr-cpinv.hpp
};
FunctionPtr<>
只包含一个FunctorBridge<>
类型的指针bridge
。
22.3 桥接接口
FunctorBridge<>
实现为一个抽象基类:
// bridge/functorbridge.hpp
template<typename R, typename... Args>
class FunctorBridge
{
public:
virtual ~FunctorBridge() {
}
virtual FunctorBridge* clone() const = 0;
virtual R invoke(Args... args) const = 0;
};
clone()
接口用来实现拷贝,invoke()
接口用来实现调用,所以FunctionPtr<>
中的拷贝构造和调用就可以像下面这样实现:
// bridge/functionptr-cpinv.hpp
template<typename R, typename... Args>
FunctionPtr<R(Args...)>::FunctionPtr(FunctionPtr const& 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)...);
}
22.4 类型擦除
为了能够接收任意函数对象,需要将FunctorBridge<>
的派生类实现为SpecificFunctorBridge<>
以接收任意函数对象:
// bridge/specificfunctorbridge.hpp
template<typename Functor, typename R, typename... Args>
class SpecificFunctorBridge : public FunctorBridge<R, Args...> {
private:
Functor functor;
public:
template<typename FunctorFwd>
SpecificFunctorBridge(FunctorFwd&& functor)
: functor(std::forward<FunctorFwd>(functor)) {
}
virtual SpecificFunctorBridge* clone() const override {
return new SpecificFunctorBridge(functor);
}
virtual R invoke(Args... args) const override {
return functor(std::forward<Args>(args)...);
}
};
FunctionPtr<>
通过任意函数对象进行构造的代码为:
// bridge/functionptr-init.hpp
template<typename R, typename... Args>
template<typename F>
FunctionPtr<R(Args...)>::FunctionPtr(F&& f)
: bridge(nullptr)
{
using Functor = std::decay_t<F>;
using Bridge = SpecificFunctorBridge<Functor, R, Args...>;
bridge = new Bridge(std::forward<F>(f));
}
SpecificFunctorBridge<>
保存了函数的类型F
,但是FunctionPtr::bridge
是抽象基类FunctorBridge<>
的指针,无法访问SpecificFunctorBridge::functor
,所以函数的类型不可见了,只能进行调用,这就是将静态多态的类型信息擦除转换为动态多态的方式。
22.5 可选的桥接
如果还需要判断两个FunctionPtr<>
是否会调用同一函数,就需要为FunctionPtr<>
重载==
运算符:
bool operator==(FunctionPtr const& f1, FunctionPtr const& f2) {
if (f1 || f2) {
return f1 && f2;
}
return f1.bridge->equals(f2.bridge);
}
bool operator!=(FunctionPtr const& f1, FunctionPtr const& f2) {
return !(f1 == f2);
}
书中的代码判断有错误,应该是只有在两个函数都不为空时才调用equals()
进行比较。然后在FunctorPtr<>
中添加接口,并在SpecificFunctorBridge<>
中实现:
template<typename R, typename... Args>
class FunctorBridge
{
public:
virtual ~FunctorBridge() {
}
virtual FunctorBridge* clone() const = 0;
virtual R invoke(Args... args) const = 0;
virtual bool equals(FunctorBridge const* fb) const = 0;
};
template<typename Functor, typename R, typename... Args>
class SpecificFunctorBridge : public FunctorBridge<R, Args...> {
private:
Functor functor;
public:
template<typename FunctorFwd>
SpecificFunctorBridge(FunctorFwd&& functor)
: functor(std::forward<FunctorFwd>(functor)) {
}
virtual SpecificFunctorBridge* clone() const override {
return new SpecificFunctorBridge(functor);
}
virtual R invoke(Args... args) const override {
return functor(std::forward<Args>(args)...);
}
virtual bool equals(FunctorBridge<R, Args...> const* fb) const override {
if (auto specFb = dynamic_cast<SpecificFunctorBridge const*>(fb)) {
return functor == specFb->functor;
}
// functors with different types are never equal:
return false;
}
};
这里的问题在于要求Functor
必须实现了==
运算符,这对函数指针没有问题,有问题的是lambda表达式,为了避免编译错误,可以通过基于SFINAE规则的类型特征模板解决:
// bridge/isequalitycomparable.hpp
#include <utility> // for declval()
#include <type_traits> // for true_type and false_type
template<typename T>
class IsEqualityComparable
{
private:
// test convertibility of == and ! == to bool:
static void* conv(bool); // to check convertibility to bool
template<typename U>
static std::true_type test(decltype(conv(std::declval<U const&>() ==
std::declval<U const&>())),
decltype(conv(!(std::declval<U const&>() ==
std::declval<U const&>())))
);
// fallback:
template<typename U>
static std::false_type test(...);
public:
static constexpr bool value = decltype(test<T>(nullptr, nullptr))::value;
};
// bridge/tryequals.hpp
#include <exception>
#include "isequalitycomparable.hpp"
template<typename T, bool EqComparable = IsEqualityComparable<T>::value>
struct TryEquals
{
static bool equals(T const& x1, T const& x2) {
return x1 == x2;
}
};
class NotEqualityComparable : public std::exception
{
};
template<typename T>
struct TryEquals<T, false>
{
static bool equals(T const& x1, T const& x2) {
throw NotEqualityComparable();
}
};
最终虚函数equals()
实现为:
template<typename Functor, typename R, typename... Args>
class SpecificFunctorBridge : public FunctorBridge<R, Args...> {
private:
Functor functor;
public:
template<typename FunctorFwd>
SpecificFunctorBridge(FunctorFwd&& functor)
: functor(std::forward<FunctorFwd>(functor)) {
}
virtual SpecificFunctorBridge* clone() const override {
return new SpecificFunctorBridge(functor);
}
virtual R invoke(Args... args) const override {
return functor(std::forward<Args>(args)...);
}
virtual bool equals(FunctorBridge<R, Args...> const* fb) const override {
if (auto specFb = dynamic_cast<SpecificFunctorBridge const*>(fb)) {
return TryEquals<Functor>::equals(functor, specFb->functor);
}
// functors with different types are never equal:
return false;
}
};
22.6 性能方面的考虑
本章中实现的FunctionPtr<>
虽然是模板,最终却是通过bridge
变量实现的函数调用,完整了静态多态到动态多态的转换,这样做是否值得需要同时考量虚函数调用的开销和调用函数对象的开销。