Skip to content

Type Erasure ​

用「多型 + 模板」把任意型別包成同一個型別,也就是 std::function 的做法。

結構 ​

cpp
struct CallableBase {                     // 1. 抽象介面
    virtual ~CallableBase() = default;
    virtual void call() = 0;
    virtual std::unique_ptr<CallableBase> clone() const = 0;
};

template <typename F>
struct CallableImpl : CallableBase {      // 2. 模板實作,記住真正的型別
    F f;
    explicit CallableImpl(F f) : f(std::move(f)) {}
    void call() override { f(); }
    std::unique_ptr<CallableBase> clone() const override {
        return std::make_unique<CallableImpl>(*this);
    }
};

class Callable {                          // 3. 對外的值型別
public:
    template <typename F,
              typename = std::enable_if_t<!std::is_same_v<std::decay_t<F>, Callable>>>
    Callable(F f) : p_(std::make_unique<CallableImpl<F>>(std::move(f))) {}
    Callable(const Callable& o) : p_(o.p_ ? o.p_->clone() : nullptr) {}
    Callable& operator=(const Callable& o) {
        Callable tmp(o);
        p_.swap(tmp.p_);
        return *this;
    }
    Callable(Callable&&) = default;
    Callable& operator=(Callable&&) = default;
    void operator()() { if (p_) p_->call(); }

private:
    std::unique_ptr<CallableBase> p_;
};

重點 ​

  • 比起直接 template <typename F> void foo(F),型別錯誤會在呼叫端就報出來
  • unique_ptr 不能 copy → 需要 virtual clone()
  • 模板 constructor 要排除 Callable 自己,否則 copy 非 const 物件會優先匹配模板,包成 CallableImpl<Callable> 並無限遞迴
  • 進一步:支援 Callable<R(Args...)> 簽名

參考 ​

頁面歷史

Just observe 👀