簡介 C++ 的 Type Erase (用多型和模板做 Duck Type)
起點
讓我們先從 template 出發:foo 需要一個 callback function。
template<typename Func>
void foo(Func callback) {
// ...
callback();
}
但是這會讓編譯錯誤訊息有點模糊:假如 callback 並不是一個可以呼叫的函數指標,或者並不是一個 callable object ,那編譯器會說錯出在第四行。但是我們都希望,編譯器在呼叫函數時就幫我們指出:這不是 foo 想要的 callback 參數。
STL 裡面就有提供 std::function<void()>
可以存下 foo 所需要的 callback,這就會倒出一個問題:我們要怎麼做出 std::function<void()>
?
以下先忽略 private 包裝和 move,這些不是這文章的重點。
用 class 包裝?
我們先捏一個 struct:
template <typename Func>
struct Callable {
Func func_;
explicit Callable(Func func) : func_(func) { }
void operator()() {
func_();
}
};
可以用 Callable 包起來的 Func ,肯定可以被我們用 func_()
的方式呼叫,可是這仍然沒辦法當作一個完整 Type – 我們仍然缺一個 template parameter。
多型?
但我們可以在上面再加蓋一層:
struct CallableType {
CallableType() = default;
virtual ~CallableType() = default;
virtual void operator()() = 0;
};
template <typename Func>
struct Callable : public CallableType {
Func func_;
Callable(Func func) : func_(func) { }
void operator()() {
func_();
}
};
要使用 CallableType 的話仍然需要一層指標讓多型 work,這樣用起來很麻煩。所以我們再包一層:CallableObj,並且讓型別在 constructor 就自動辨別好。
struct CallableObj {
std::unique_ptr<CallableType> callableType_;
template <typename Func>
CallableObj(Func func)
: callableType_(std::make_unique<Callable<Func>>(func)) { }
void operator()() {
(*callableType_)();
}
};
這時,我們便可以用 CallableObj 當作 foo 的參數了! … 嗎???
void foo(Func callback) {
// ...
callback();
}
Copy
因為 CallableObj 裡面有一個 std::unique_ptr<CallableType>
,所以 CallableObj 是複製不了的。
function "CallableObj::CallableObj(const CallableObj &)"
(declared implicitly) cannot be referenced -- it is a deleted function
我們需要繼承 CallableType 的 Callable<Func> 提供一個 clone() 的介面,讓我們可以好好的複製被 CallableType 藏起來的東西,也就是 Func。
struct CallableType {
// ...
virtual std::unique_ptr<CallableType> clone() const = 0;
};
template <typename Func>
struct Callable : public CallableType {
// ...
// just copy
std::unique_ptr<CallableType> clone() const override {
return std::make_unique<Callable<Func>>(*this);
}
};
所以我們就可以把 CallableObj 的 copy constructor/assignment 補上
CallableObj(const CallableObj& other):
callableType_(other.callableType_->clone()) { }
CallableObj& operator=(const CallableObj& other) {
CallableObj tmp(other);
callableType_.swap(tmp.callableType_);
return *this;
}
Code
最後,再補上 move constructor/assignment 和 null pointer check。
Code:
#include <iostream>
#include <memory>
struct CallableType {
CallableType() = default;
virtual ~CallableType() = default;
virtual void operator()() = 0;
virtual std::unique_ptr<CallableType> clone() const = 0;
};
template <typename Func>
struct Callable : public CallableType {
Func func_;
explicit Callable(Func func) : func_(func) { }
void operator()() {
func_();
}
std::unique_ptr<CallableType> clone() const override {
return std::make_unique<Callable<Func>>(*this);
}
};
struct CallableObj {
std::unique_ptr<CallableType> callableType_;
template <typename Func>
CallableObj(Func func)
: callableType_(std::make_unique<Callable<Func>>(func)) { }
~CallableObj() = default;
CallableObj(CallableObj&&) = default;
CallableObj& operator=(CallableObj&&) = default;
CallableObj(const CallableObj& other) {
if (other.callableType_) {
callableType_ = other.callableType_->clone();
}
}
CallableObj& operator=(const CallableObj& other) {
CallableObj tmp(other);
callableType_.swap(tmp.callableType_);
return *this;
}
void operator()() {
if (callableType_) {
(*callableType_)();
}
}
};
void foo(CallableObj callback) {
callback();
}
int main() {
CallableObj co([]() { std::cout << "hello\n"; });
foo(co); // output hello
foo(std::move(co)); // output hello
foo(co); // no output
return 0;
}
More...?
所以,還有那些沒做到?
Private 包裝:func_ 和 callableType_ 應該要是 private member
Func 要考慮 move semantic
要怎麼用 Template 做到
CallableObj<RetType(ArgType1, ArgType2, ...)>