https://blog.the-pans.com/type-erasure/
C++ Type Erasure,C++类型消除,是一种常用的范式,用于在C++中实现类型安全的类Duck Typing。
考虑一下下面的接口和问题:
class Shelf {...public:void forEachBook(??? f); // 这里的 ??? 应该怎么定义才能让用户完成如下调用?...}shelf.forEachBook([](auto&& book){ book.print(); });
以及:
std::vector<???> printables; // ??? is some type that defines operator()for (auto printable : printables) {printable();}
模板
可以将forEachBook定义成模板方法:
template<typename T>void forEachBook(T& f);
但是,这样做有几个模板导致的问题:
- 这个方法实现必须写在头文件里(所有模板方法实现都是如此);
- 调用方如果使用多个lambda调用该方法,每一个lambda定义都会导致方法被模板特化一次;
抽象基类
另一个思路是,可以让forEachBook接受一个抽象基类,调用方需要实现子类并传入:
class AbstractFoo {virtual void operator()(Book&& book) = 0;virtual ~AbstractFoo();}void forEachBook(AbstractFoo& f);
但是,这样做也有以下问题:
- 破坏了duck typing模式,lambda并不继承自任何基类,无法直接将lambda传入该方法;
- 此外,可能有部分已有代码是满足duck typing,但没有上述继承关系的,因此,可能需要自己封装adapter;
Type Erasing
class AbstractFoo {public:virtual void operator()(Book &&book) = 0;virtual ~AbstractFoo();}template<typename T>class AbstractFooWrapper : AbstractFoo {public:AbstractFooWrapper(t): t(t) {}void operator()(Book &&book) {t(book);}private:T t;}class FinalFoo {public:template<typaname T>FinalFoo(T t) { wrapper.reset(new AbstractFooWrapper<T>(t));}void operator()(Book &&book) {wrapper->operator()(book);}private:std::unique_ptr<AbstractFoo> wrapper;}void forEachBook(FinalFoo& f);
使用上面的写法:
- FinalFoo本身并不会成为一个template class,只有它的Copy Constructor是一个template function,因此用多种类型创建FinalFoo只会导致Copy Constructor被生成多份;
- FinalFoo可以包装任意的类型T,并且由于模板实现代码的限制,T必须是一个接受Book&&作为参数的可调用对象,调用FinalFoo的operator()就会调用T;
- 类似的,我们还可以在FinalFoo中添加更多的duck-typing约束,只要像上面一样:
- 在AbstractFoo中声明virtual type-typeing约束方法;
- 在AbstractFooWrapper中利用模板类型变量t实现type-typing方法;
- 在FinalFoo的Copy Constructor中接受模板类型,并定义type-typing方法,利用指针调用AbstractFoo实现。
TypeErasing的其他应用
std::function
std::function用到了type erasure,下面是一个 over simplified 示例:
template<typename R, typename... Args>struct callable_base {virtual R operator()(Args...) = 0;virtual ~callable_base() {}};template<typename F, typename R, typename... Args>struct callable : callable_base<R, Args...> {F functor;callable(F functor) : functor(functor) {}virtual R operator()(Args... args) { return functor(args...); }};template <typename T> class function;template <typename R, typename... Args>class function<R(Args...)> {std::unique_ptr<callable_base<R, Args...>> c;public:template <typename F>function(F f) {c.reset(new callable<F, R, Args...>(f));}R operator()(Args... args) { return (*c)(args...); }// ...};int returnInt() {return 2;}auto lambda = []() -> int {return 3;};function<int()> f1(lambda);function<int()> f2(returnInt);
