https://blog.the-pans.com/type-erasure/

C++ Type Erasure,C++类型消除,是一种常用的范式,用于在C++中实现类型安全的类Duck Typing。

考虑一下下面的接口和问题:

  1. class Shelf {
  2. ...
  3. public:
  4. void forEachBook(??? f); // 这里的 ??? 应该怎么定义才能让用户完成如下调用?
  5. ...
  6. }
  7. shelf.forEachBook([](auto&& book){ book.print(); });

以及:

  1. std::vector<???> printables; // ??? is some type that defines operator()
  2. for (auto printable : printables) {
  3. printable();
  4. }

模板

可以将forEachBook定义成模板方法:

  1. template<typename T>
  2. void forEachBook(T& f);
  1. 但是,这样做有几个模板导致的问题:
  1. 这个方法实现必须写在头文件里(所有模板方法实现都是如此);
  2. 调用方如果使用多个lambda调用该方法,每一个lambda定义都会导致方法被模板特化一次;

抽象基类

另一个思路是,可以让forEachBook接受一个抽象基类,调用方需要实现子类并传入:

  1. class AbstractFoo {
  2. virtual void operator()(Book&& book) = 0;
  3. virtual ~AbstractFoo();
  4. }
  5. void forEachBook(AbstractFoo& f);

但是,这样做也有以下问题:

  1. 破坏了duck typing模式,lambda并不继承自任何基类,无法直接将lambda传入该方法;
  2. 此外,可能有部分已有代码是满足duck typing,但没有上述继承关系的,因此,可能需要自己封装adapter;

Type Erasing

  1. class AbstractFoo {
  2. public:
  3. virtual void operator()(Book &&book) = 0;
  4. virtual ~AbstractFoo();
  5. }
  6. template<typename T>
  7. class AbstractFooWrapper : AbstractFoo {
  8. public:
  9. AbstractFooWrapper(t): t(t) {}
  10. void operator()(Book &&book) {t(book);}
  11. private:
  12. T t;
  13. }
  14. class FinalFoo {
  15. public:
  16. template<typaname T>
  17. FinalFoo(T t) { wrapper.reset(new AbstractFooWrapper<T>(t));}
  18. void operator()(Book &&book) {wrapper->operator()(book);}
  19. private:
  20. std::unique_ptr<AbstractFoo> wrapper;
  21. }
  22. void forEachBook(FinalFoo& f);

使用上面的写法:

  1. FinalFoo本身并不会成为一个template class,只有它的Copy Constructor是一个template function,因此用多种类型创建FinalFoo只会导致Copy Constructor被生成多份;
  2. FinalFoo可以包装任意的类型T,并且由于模板实现代码的限制,T必须是一个接受Book&&作为参数的可调用对象,调用FinalFoo的operator()就会调用T;
  3. 类似的,我们还可以在FinalFoo中添加更多的duck-typing约束,只要像上面一样:
    1. 在AbstractFoo中声明virtual type-typeing约束方法;
    2. 在AbstractFooWrapper中利用模板类型变量t实现type-typing方法;
    3. 在FinalFoo的Copy Constructor中接受模板类型,并定义type-typing方法,利用指针调用AbstractFoo实现。

TypeErasing的其他应用

std::function

std::function用到了type erasure,下面是一个 over simplified 示例:

  1. template<typename R, typename... Args>
  2. struct callable_base {
  3. virtual R operator()(Args...) = 0;
  4. virtual ~callable_base() {}
  5. };
  6. template<typename F, typename R, typename... Args>
  7. struct callable : callable_base<R, Args...> {
  8. F functor;
  9. callable(F functor) : functor(functor) {}
  10. virtual R operator()(Args... args) { return functor(args...); }
  11. };
  12. template <typename T> class function;
  13. template <typename R, typename... Args>
  14. class function<R(Args...)> {
  15. std::unique_ptr<callable_base<R, Args...>> c;
  16. public:
  17. template <typename F>
  18. function(F f) {
  19. c.reset(new callable<F, R, Args...>(f));
  20. }
  21. R operator()(Args... args) { return (*c)(args...); }
  22. // ...
  23. };
  24. int returnInt() {
  25. return 2;
  26. }
  27. auto lambda = []() -> int {
  28. return 3;
  29. };
  30. function<int()> f1(lambda);
  31. function<int()> f2(returnInt);