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 → 需要 virtualclone()- 模板 constructor 要排除
Callable自己,否則 copy 非 const 物件會優先匹配模板,包成CallableImpl<Callable>並無限遞迴 - 進一步:支援
Callable<R(Args...)>簽名