CTTCG 22 Bridging Static and Dynamic Polymorphism

类型擦除

主要讲的是类型擦除,例子是 std::function 类模板。特殊情况:std::function<void(void)>std::function<void()> 是同一个类型。

实现思路

  1. std::function 原始模板捕获了函数类型。
template<typename Signature>
class FunctionPtr;
  1. std::function 的偏特化能够获得函数的返回值和函数参数包的类型。
template<typename R, typename... Args>
class FunctionPtr<R(Args...)> { ... }
  1. std::function 包含了一个函数对象基类的指针,这个基类指针有一个 invoke 虚函数,返回值和函数参数类型与模板参数对应。std::function 被调用时,会将调用转发到这个函数对象上。
  2. 实现构造模板函数、赋值模板操作符时,捕获传入的对象,生成一个继承于函数对象基类(之前提到过)的子类对象,其 invoke 虚函数的实现就是转而调用这个对象() 操作符。
template<typename Functor, typename R, typename... Args>
class SpecificFunctorBridge : public FunctorBridge<R, Args...> {
    Functor functor;
    ...
}

子类对象就是通过上面的模板创建的。模板参数 Functor 对应于捕获对象的存储类型,书中的定义为 using Functor = std::decay_t<F>F 为捕获对象的真实类型)。

书中还实现了 FunctionPtr 的比较操作符,当捕获对象不支持 == 时,将会在运行时抛出异常。std::function 只对和空指针的比较实现了 == 操作符。

性能

主要考虑动态分配内存虚函数调用两个环节。

约束检查

类型擦除不代表将静态语言变成动态语言。使用 Boost 的 TypeErasure 库:

using AnyPrintable = any<mpl::vector<
                           copy_constructible<>,
                           typeid_<>,
                           ostreamable<>
                        >>;

这对里面可包含的类型进行了约束检查,不满足约束的类型赋值(或构造)会在编译期被拒绝。

实现细节: 用 swap 实现赋值操作符

可以复用构造函数

  1. 创建临时对象(在移动赋值且参数类型与目的类型相同时可以省略这一步)
  2. 引入 std::swap(可选)
  3. 调用 swap

libstdc++ 中的 std::function 实现

参考另外一篇笔记。

一个简单的实现

https://godbolt.org/z/dqTWW4733

#include <iostream>
#include <memory>

template <typename FuncT>
struct Function;

template <typename R, typename... Args>
class Function<R(Args...)> {
   public:
    Function() = default;

    template <typename T>
    Function(T obj) {
        reset(std::move(obj));
    }

    // TODO: operator=.
    // TODO: move/copy constructors.

    R operator()(Args &&...args) {
        if (!impl) {
            throw std::runtime_error{"no associated function object"};
        }
        return impl->operator()(std::forward<Args>(args)...);
    }

   private:
    template <typename R1, typename... Args1>
    struct Impl {
        virtual R1 operator()(Args1 &&...args) = 0;
        virtual ~Impl() = default;
    };

    template <typename T>
    void reset(T obj) {
        struct DerivedImpl : Impl<R, Args...> {
            DerivedImpl(T obj) : obj_(std::move(obj)) {}
            R operator()(Args &&...args) override {
                return obj_(std::forward<Args>(args)...);
            };
            T obj_;
        };
        
        //- TODO: handle exception.
        //- impl.reset((Impl<R, Args...> *)new DerivedImpl{std::move(obj)});
        //+ std::unique_ptr<Base> 可以从 std::unique_ptr<Derived> 转换而来!
        impl = std::make_unique<DerivedImpl>(std::move(obj));
    }

    std::unique_ptr<Impl<R, Args...>> impl;
};

int main(int argc, char **argv) {
    Function<int(int)> identity = [](int a) { return a; };
    Function<int(int)> twice = [](int a) { return a * 2; };
    std::cout << identity(3) << std::endl;
    std::cout << twice(3) << std::endl;
    Function<void(std::string const &)> println = [](std::string const &s) {
        std::cout << s << std::endl;
    };
    println("hello");
}