[英]Move-only version of std::function
因為std::function
是可復制的,所以標准要求用於構造它的可調用對象也是可復制的:
n337 (20.8.11.2.1)
template<class F> function(F f);
要求:
F
應為 CopyConstructible。f
對於參數類型ArgTypes
和返回類型R
應是 Callable (20.8.11.2)。 A 的復制構造函數和析構函數不應拋出異常。
這意味着不可能從不可復制的綁定對象或捕獲諸如std::unique_ptr
之類的僅移動類型的 lambda 形成std::function
。
似乎可以為只移動的可調用對象實現這樣的只移動包裝器。 是否有std::function
的標准庫僅移動等效項,或者是否有解決此問題的通用解決方法?
不,C++ std
庫中沒有std::function
的僅移動版本。 (從 C++14 開始)
最快的委托是一個類似std::function
的類的實現,它恰好比許多std
庫中的大多數std::function
實現更快,並且它應該很容易分叉成move
和copy
版本。
使用轉發operator()
將僅move
函數對象包裝到類中的shared_ptr<F>
是另一種方法。
這是一個task
草圖:
template<class Sig>
struct task;
namespace details {
template<class Sig>
struct task_iimpl;
template<class R, class...Args>
struct task_iimpl<R(Args...)> {
virtual ~task_iimpl() {}
virtual R invoke(Args&&...args) const = 0;
};
template<class F, class Sig>
struct task_impl;
template<class F, class R, class...Args>
struct task_impl<F,R(Args...)>:
task_iimpl<R(Args...)>
{
F f;
template<class T>
task_impl(T&& t):f(std::forward<T>(t)) {}
virtual R invoke(Args&&...args) const override {
return f( std::forward<Args>(args...) );
}
};
template<class F, class...Args>
struct task_impl<F,void(Args...)>:
task_iimpl<void(Args...)>
{
F f;
template<class T>
task_impl(T&& t):f(std::forward<T>(t)) {}
virtual void invoke(Args&&...args) const override {
f( std::forward<Args>(args...) );
}
};
}
template<class R, class...Args>
struct task<R(Args...)> {
virtual ~task_iimpl() {}
R operator()(Args...args) const {
return pImpl->invoke(std::forward<Args>(args...));
}
explicit operator bool()const{ return static_cast<bool>(pImpl); }
task(task &&)=default;
task& operator=(task &&)=default;
task()=default;
// and now for a mess of constructors
// the rule is that a task can be constructed from anything
// callable<R(Args...)>, destroyable, and can be constructed
// from whatever is passed in. The callable feature is tested for
// in addition, if constructed from something convertible to `bool`,
// then if that test fails we construct an empty task. This makes us work
// well with empty std::functions and function pointers and other tasks
// that are call-compatible, but not exactly the same:
struct from_func_t {};
template<class F,
class dF=std::decay_t<F>,
class=std::enable_if_t<!std::is_same<dF, task>{}>,
class FR=decltype(std::declval<F const&>()(std::declval<Args>()...)),
std::enable_if_t<std::is_same<R, void>{} || std::is_convertible<FR, R>{} >*=0,
std::enable_if_t<std::is_convertible<dF, bool>{}>*=0
>
task(F&& f):
task(
static_cast<bool>(f)?
task( from_func_t{}, std::forward<F>(f) ):
task()
)
{}
template<class F,
class dF=std::decay_t<F>,
class=std::enable_if_t<!std::is_same<dF, task>{}>,
class FR=decltype(std::declval<F const&>()(std::declval<Args>()...)),
std::enable_if_t<std::is_same<R, void>{} || std::is_convertible<FR, R>{} >*=0,
std::enable_if_t<!std::is_convertible<dF, bool>{}>*=0
>
task(F&& f):
task( from_func_t{}, std::forward<F>(f) )
{}
task(std::nullptr_t):task() {}
// overload resolution helper when signatures match exactly:
task( R(*pf)(Args...) ):
task( pf?task( from_func_t{}, pf ):task() )
{}
private:
template<class F,
class dF=std::decay_t<F>
>
task(from_func_t, F&& f):
pImpl( std::make_unique<details::task_impl<dF,R(Args...)>>(
std::forward<F>(f)
)
{}
std::unique_ptr<details::task_iimpl<R(Args...)> pImpl;
};
但它沒有經過測試或編譯,我只是寫了它。
更工業化的版本將包括一個小型緩沖區優化 (SBO) 來存儲小的可調用對象(假設它們是可移動的;如果不可移動,則存儲在堆上以允許移動),以及一個 get-pointer-if-you-guess-the-類型正確(如std::function
)。
正如其他人指出的那樣,庫中沒有std::function
的僅移動版本。 以下是重用(濫用?) std::function
並允許它接受僅移動類型的解決方法。 它在很大程度上受到dyp 在評論中的實施的啟發,所以很多功勞歸功於他:
#include <functional>
#include <iostream>
#include <type_traits>
#include <utility>
template<typename T>
class unique_function : public std::function<T>
{
template<typename Fn, typename En = void>
struct wrapper;
// specialization for CopyConstructible Fn
template<typename Fn>
struct wrapper<Fn, std::enable_if_t< std::is_copy_constructible<Fn>::value >>
{
Fn fn;
template<typename... Args>
auto operator()(Args&&... args) { return fn(std::forward<Args>(args)...); }
};
// specialization for MoveConstructible-only Fn
template<typename Fn>
struct wrapper<Fn, std::enable_if_t< !std::is_copy_constructible<Fn>::value
&& std::is_move_constructible<Fn>::value >>
{
Fn fn;
wrapper(Fn&& fn) : fn(std::forward<Fn>(fn)) { }
wrapper(wrapper&&) = default;
wrapper& operator=(wrapper&&) = default;
// these two functions are instantiated by std::function
// and are never called
wrapper(const wrapper& rhs) : fn(const_cast<Fn&&>(rhs.fn)) { throw 0; } // hack to initialize fn for non-DefaultContructible types
wrapper& operator=(wrapper&) { throw 0; }
template<typename... Args>
auto operator()(Args&&... args) { return fn(std::forward<Args>(args)...); }
};
using base = std::function<T>;
public:
unique_function() noexcept = default;
unique_function(std::nullptr_t) noexcept : base(nullptr) { }
template<typename Fn>
unique_function(Fn&& f) : base(wrapper<Fn>{ std::forward<Fn>(f) }) { }
unique_function(unique_function&&) = default;
unique_function& operator=(unique_function&&) = default;
unique_function& operator=(std::nullptr_t) { base::operator=(nullptr); return *this; }
template<typename Fn>
unique_function& operator=(Fn&& f)
{ base::operator=(wrapper<Fn>{ std::forward<Fn>(f) }); return *this; }
using base::operator();
};
using std::cout; using std::endl;
struct move_only
{
move_only(std::size_t) { }
move_only(move_only&&) = default;
move_only& operator=(move_only&&) = default;
move_only(move_only const&) = delete;
move_only& operator=(move_only const&) = delete;
void operator()() { cout << "move_only" << endl; }
};
int main()
{
using fn = unique_function<void()>;
fn f0;
fn f1 { nullptr };
fn f2 { [](){ cout << "f2" << endl; } }; f2();
fn f3 { move_only(42) }; f3();
fn f4 { std::move(f2) }; f4();
f0 = std::move(f3); f0();
f0 = nullptr;
f2 = [](){ cout << "new f2" << endl; }; f2();
f3 = move_only(69); f3();
return 0;
}
是的,在當前的 C++23 草案中有一個關於 std::move_only_function的提案,於 2021-10 年通過:
本文提出了一個保守的、只移動的
std::function
等價物。
另請參閱std::move_only_function
上的 cppreference 條目:
類模板
std::move_only_function
是一個通用的多態函數包裝器。std::move_only_function
對象可以存儲和調用任何可構造的(不需要是可移動構造的)可調用目標——函數、lambda 表達式、綁定表達式或其他函數對象,以及指向成員函數的指針和指向成員對象的指針。
...
std::move_only_function
滿足MoveConstructible和MoveAssignable的要求,但不滿足CopyConstructible或CopyAssignable 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.