Denn std::function
kopierbar ist, verlangt der Standard, dass Callables, die zum Erstellen verwendet werden, auch kopierbar sein müssen:
n337 (20.8.11.2.1)
template<class F> function(F f);
Erfordert: F
muss CopyConstructible sein. f
muss Callable (20.8.11.2) für Argumenttypen sein ArgTypes
und Rückgabetyp R
. Der Kopierkonstruktor und -destruktor von A dürfen keine Ausnahmen auslösen.`
Dies impliziert, dass es nicht möglich ist, eine zu bilden std::function
von einem nicht kopierbaren Bindungsobjekt oder einem Lambda, das einen Nur-Verschieben-Typ wie z std::unique_ptr
.
Es scheint möglich, einen solchen Move-Only-Wrapper für Move-Only-Callables zu implementieren. Gibt es ein Äquivalent für eine Standardbibliothek, die nur zum Verschieben verwendet wird? std::function
oder gibt es eine allgemeine Problemumgehung für dieses Problem?
Nein, es gibt keine Move-Only-Version von std::function
im C++ std
Bücherei. (ab C++14)
Schnellstmögliche Delegierte ist eine Implementierung von a std::function
wie Klasse, die zufällig schneller ist als die meisten anderen std::function
Implementierungen in vielen std
Bibliotheken, und es sollte einfach sein, sich in a zu verzweigen move
und copy
Ausführung.
Verpacken Sie Ihre move
nur Funktionsobjekt in a shared_ptr<F>
in einer Klasse mit einer Weiterleitung operator()
ist ein weiterer Ansatz.
Hier ist ein task
skizzieren:
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>
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>
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;
};
aber es wurde nicht getestet oder kompiliert, ich habe es einfach geschrieben.
Eine industrietauglichere Version würde eine Small Buffer Optimization (SBO) beinhalten, um kleine Callables zu speichern (vorausgesetzt, sie sind verschiebbar; wenn sie nicht verschiebbar sind, speichern Sie sie auf dem Heap, um das Verschieben zu ermöglichen) und einen Get-Zeiger-wenn-Sie-den-vermuten- Typ-Recht (wie std::function
).
Wie andere bereits angemerkt haben, gibt es keine reine Move-Version von std::function
in der Bücherei. Im Folgenden finden Sie eine Problemumgehung, bei der die Wiederverwendung (Missbrauch?) std::function
und ermöglicht es, Nur-Bewegungstypen zu akzeptieren. Es ist weitgehend inspiriert von Implementierung von dyp in den Kommentaren, also gebührt ihm viel Anerkennung:
#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;
}
Arbeitsversion zu coliru.
Ja, es gibt eine Vorschlag für std::move_only_function im aktuellen Entwurf von C++23, angenommen 2021-10:
Dieses Papier schlägt ein konservatives Äquivalent von nur Bewegungen vor std::function
.
Siehe auch die cppreference-Eintrag auf std::move_only_function:
Das Klassen-Template std::move_only_function ist ein Allzweck-Wrapper für polymorphe Funktionen. std::move_only_function-Objekte können jedes konstruierbare (muss nicht move-konstruierbar sein) aufrufbare Ziel speichern und aufrufen – Funktionen, Lambda-Ausdrücke, Bindungsausdrücke oder andere Funktionsobjekte sowie Zeiger auf Member-Funktionen und Zeiger auf Member-Objekte.
…
std::move_only_function erfüllt die Anforderungen von MoveConstructible und MoveAssignable, aber nicht CopyConstructible oder CopyAssignable.
.
std::function
ist auf verschiedene Arten kaputt … Ich denke, das ist allgemein akzeptiert, aber sehr schwer zu beheben, ohne vorhandenen Code zu beschädigen.– Kerrek SB
15. August 14 um 17:06 Uhr
Hallo. Danke für den Kommentar. Jetzt, wo Sie es erwähnen, wäre es schön, einige spezifische Arten zu hören, auf die es gebrochen ist.
– orm
15. August 14 um 17:09 Uhr
@KerrekSB glaube ich nicht das bestimmten Aspekt ist gebrochen. Seit
function
Typlöschung durchführt, würde es zu einem Laufzeitproblem, ob dies der Fall ist oder nicht Beispiel vonfunction
ist kopierbar.– dyp
15. August 14 um 19:34 Uhr
Ja, du kannst Wiederverwendung
std::function
damit es funktioniert.. irgendwie (beachten Sie dasfunction_mo
selbst ist nur bewegungsfähig, also keine Ausnahmen vonhack
wird geworfen).– dyp
15. August 14 um 19:47 Uhr
@orm: Einer der großen Knackpunkte ist, dass der Funktionsaufrufoperator ist
const
, was die Bibliothek benötigt, um Thread-sicher zu sein. Dies macht es schwierig für Leute, die es verwenden möchtenfunction<void()>
als generisches aufrufbares Ding in gleichzeitigen Einstellungen. Ein weiterer Aspekt, der etwas unausgegoren ist, ist die Unterstützung für typgelöschte Zuweisungen, glaube ich (insbesondere in Bezug auf ausgefallene Zeiger);function
ist die einzige Klasse in der Bibliothek, die eine typgelöschte Zuweisung hat und auch kopierbar ist. (Siehe N3916 für einige Aspekte. N4041 ist auch interessant.)– Kerrek SB
15. August 14 um 23:02 Uhr