一個只移動的 stdfunction
std::function
型別可以刪除一些操作。它需要的一件事是儲存的值是可複製的。
這會在少數情況下引起問題,例如儲存唯一 ptrs 的 lambda。如果你在複製無關緊要的上下文中使用 std::function
,例如將任務分派給執行緒的執行緒池,則此要求可能會增加開銷。
特別是,std::packaged_task<Sig>
是一個只能移動的可呼叫物件。你可以在 std::packaged_task<
void(Args…)>
中儲存一個 std::packaged_task<
R(Args…)>
,但這是一個非常重量級和模糊的方法來建立一個只能移動的可呼叫型別擦除類。
因此 task
。這演示瞭如何編寫簡單的 std::function
型別。我省略了複製建構函式(這也涉及到 details::task_pimpl<...>
中新增 clone
方法)。
template<class Sig>
struct task;
// putting it in a namespace allows us to specialize it nicely for void return value:
namespace details {
template<class R, class...Args>
struct task_pimpl {
virtual R invoke(Args&&...args) const = 0;
virtual ~task_pimpl() {};
virtual const std::type_info& target_type() const = 0;
};
// store an F. invoke(Args&&...) calls the f
template<class F, class R, class...Args>
struct task_pimpl_impl:task_pimpl<R,Args...> {
F f;
template<class Fin>
task_pimpl_impl( Fin&& fin ):f(std::forward<Fin>(fin)) {}
virtual R invoke(Args&&...args) const final override {
return f(std::forward<Args>(args)...);
}
virtual const std::type_info& target_type() const final override {
return typeid(F);
}
};
// the void version discards the return value of f:
template<class F, class...Args>
struct task_pimpl_impl<F,void,Args...>:task_pimpl<void,Args...> {
F f;
template<class Fin>
task_pimpl_impl( Fin&& fin ):f(std::forward<Fin>(fin)) {}
virtual void invoke(Args&&...args) const final override {
f(std::forward<Args>(args)...);
}
virtual const std::type_info& target_type() const final override {
return typeid(F);
}
};
};
template<class R, class...Args>
struct task<R(Args...)> {
// semi-regular:
task()=default;
task(task&&)=default;
// no copy
private:
// aliases to make some SFINAE code below less ugly:
template<class F>
using call_r = std::result_of_t<F const&(Args...)>;
template<class F>
using is_task = std::is_same<std::decay_t<F>, task>;
public:
// can be constructed from a callable F
template<class F,
// that can be invoked with Args... and converted-to-R:
class= decltype( (R)(std::declval<call_r<F>>()) ),
// and is not this same type:
std::enable_if_t<!is_task<F>{}, int>* = nullptr
>
task(F&& f):
m_pImpl( make_pimpl(std::forward<F>(f)) )
{}
// the meat: the call operator
R operator()(Args... args)const {
return m_pImpl->invoke( std::forward<Args>(args)... );
}
explicit operator bool() const {
return (bool)m_pImpl;
}
void swap( task& o ) {
std::swap( m_pImpl, o.m_pImpl );
}
template<class F>
void assign( F&& f ) {
m_pImpl = make_pimpl(std::forward<F>(f));
}
// Part of the std::function interface:
const std::type_info& target_type() const {
if (!*this) return typeid(void);
return m_pImpl->target_type();
}
template< class T >
T* target() {
return target_impl<T>();
}
template< class T >
const T* target() const {
return target_impl<T>();
}
// compare with nullptr :
friend bool operator==( std::nullptr_t, task const& self ) { return !self; }
friend bool operator==( task const& self, std::nullptr_t ) { return !self; }
friend bool operator!=( std::nullptr_t, task const& self ) { return !!self; }
friend bool operator!=( task const& self, std::nullptr_t ) { return !!self; }
private:
template<class T>
using pimpl_t = details::task_pimpl_impl<T, R, Args...>;
template<class F>
static auto make_pimpl( F&& f ) {
using dF=std::decay_t<F>;
using pImpl_t = pimpl_t<dF>;
return std::make_unique<pImpl_t>(std::forward<F>(f));
}
std::unique_ptr<details::task_pimpl<R,Args...>> m_pImpl;
template< class T >
T* target_impl() const {
return dynamic_cast<pimpl_t<T>*>(m_pImpl.get());
}
};
為了使這個庫值得,你需要新增一個小的緩衝區優化,因此它不會在堆上儲存每個可呼叫的。
新增 SBO 將需要一個非預設的 task(task&&)
,類中的一些 std::aligned_storage_t
,一個帶有刪除器的 m_pImpl
unique_ptr
,可以設定為僅銷燬(並且不將記憶體返回到堆中),以及 emplace_move_to( void* ) = 0
中的 emplace_move_to( void* ) = 0
。
以上程式碼的例項 (沒有 SBO)。