Ich versuche, eine zu erstellen std::function
aus einem bewegungserfassenden Lambda-Ausdruck. Beachten Sie, dass ich problemlos einen bewegungserfassenden Lambda-Ausdruck erstellen kann; es ist nur, wenn ich versuche, es in ein zu wickeln std::function
dass ich einen Fehler bekomme.
Zum Beispiel:
auto pi = std::make_unique<int>(0);
// no problems here!
auto foo = [q = std::move(pi)] {
*q = 5;
std::cout << *q << std::endl;
};
// All of the attempts below yield:
// "Call to implicitly-deleted copy constructor of '<lambda...."
std::function<void()> bar = foo;
std::function<void()> bar{foo};
std::function<void()> bar{std::move(foo)};
std::function<void()> bar = std::move(foo);
std::function<void()> bar{std::forward<std::function<void()>>(foo)};
std::function<void()> bar = std::forward<std::function<void()>>(foo);
Ich werde erklären, warum ich so etwas schreiben möchte. Ich habe eine UI-Bibliothek geschrieben, die es dem Benutzer ähnlich wie jQuery oder JavaFX ermöglicht, Maus-/Tastaturereignisse im Vorbeigehen zu verarbeiten std::function
s zu Methoden mit Namen wie on_mouse_down()
, on_mouse_drag()
, push_undo_action()
etc.
Offensichtlich die std::function
Ich möchte übergeben, sollte idealerweise einen Move-Capturing-Lambda-Ausdruck verwenden, andernfalls muss ich auf die hässliche Redewendung „Release/Acquire-in-Lambda“ zurückgreifen, die ich verwendet habe, als C++11 der Standard war:
std::function<void()> baz = [q = pi.release()] {
std::unique_ptr<int> p{q};
*p = 5;
std::cout << *q << std::endl;
};
Beachten Sie das Aufrufen baz
zweimal wäre ein Fehler im obigen Code. In meinem Code wird diese Closure jedoch garantiert genau einmal aufgerufen.
Übrigens, in meinem echten Code übergebe ich keine std::unique_ptr<int>
aber etwas Interessanteres.
Schließlich verwende ich Xcode6-Beta4, das die folgende Version von Clang verwendet:
Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)
Target: x86_64-apple-darwin13.3.0
Thread model: posix
template<class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);
Erfordert: F
muss CopyConstructible
. f
muss Callable
für Argumenttypen ArgTypes
und Rückgabetyp R
. Der Kopierkonstruktor und -destruktor von A dürfen keine Ausnahmen auslösen.
§20.9.11.2.1 [func.wrap.func.con]
Beachten Sie, dass operator =
wird in Bezug auf diesen Konstruktor und definiert swap
daher gelten die gleichen Einschränkungen:
template<class F> function& operator=(F&& f);
Auswirkungen: function(std::forward<F>(f)).swap(*this);
§20.9.11.2.1 [func.wrap.func.con]
Um Ihre Frage zu beantworten: Ja, es ist möglich, a zu konstruieren std::function
von einem bewegungserfassenden Lambda (da dies nur angibt, wie das Lambda erfasst), aber es ist nicht möglich, eine zu konstruieren std::function
von einem Nur-Verschieben-Typ (z. B. ein bewegungserfassendes Lambda, das etwas bewegungserfasst, das nicht kopierkonstruierbar ist).
Als std::function<?>
den Kopierkonstruktor des gespeicherten aufrufbaren Objekts löschen muss, können Sie ihn nicht aus einem Nur-Verschieben-Typ erstellen. Ihr Lambda ist, da es einen Move-Only-Typ nach Wert erfasst, ein Move-Only-Typ. Also … Sie können Ihr Problem nicht lösen. std::function
kann Ihr Lambda nicht speichern.
Zumindest nicht direkt.
Das ist C++, wir umgehen das Problem einfach.
template<class F>
struct shared_function {
std::shared_ptr<F> f;
shared_function() = delete; // = default works, but I don't use it
shared_function(F&& f_):f(std::make_shared<F>(std::move(f_))){}
shared_function(shared_function const&)=default;
shared_function(shared_function&&)=default;
shared_function& operator=(shared_function const&)=default;
shared_function& operator=(shared_function&&)=default;
template<class...As>
auto operator()(As&&...as) const {
return (*f)(std::forward<As>(as)...);
}
};
template<class F>
shared_function< std::decay_t<F> > make_shared_function( F&& f ) {
return { std::forward<F>(f) };
}
Jetzt, da dies erledigt ist, können wir Ihr Problem lösen.
auto pi = std::make_unique<int>(0);
auto foo = [q = std::move(pi)] {
*q = 5;
std::cout << *q << std::endl;
};
std::function< void() > test = make_shared_function( std::move(foo) );
test(); // prints 5
Die Semantik von a shared_function
unterscheidet sich geringfügig von anderen Funktionen, da eine Kopie davon den gleichen Status hat (auch wenn sie in eine std::function
) als Original.
Wir können auch eine Move-only-Feuer-Einmal-Funktion schreiben:
template<class Sig>
struct fire_once;
template<class T>
struct emplace_as {};
template<class R, class...Args>
struct fire_once<R(Args...)> {
// can be default ctored and moved:
fire_once() = default;
fire_once(fire_once&&)=default;
fire_once& operator=(fire_once&&)=default;
// implicitly create from a type that can be compatibly invoked
// and isn't a fire_once itself
template<class F,
std::enable_if_t<!std::is_same<std::decay_t<F>, fire_once>{}, int> =0,
std::enable_if_t<
std::is_convertible<std::result_of_t<std::decay_t<F>&(Args...)>, R>{}
|| std::is_same<R, void>{},
int
> =0
>
fire_once( F&& f ):
fire_once( emplace_as<std::decay_t<F>>{}, std::forward<F>(f) )
{}
// emplacement construct using the emplace_as tag type:
template<class F, class...FArgs>
fire_once( emplace_as<F>, FArgs&&...fargs ) {
rebind<F>(std::forward<FArgs>(fargs)...);
}
// invoke in the case where R is not void:
template<class R2=R,
std::enable_if_t<!std::is_same<R2, void>{}, int> = 0
>
R2 operator()(Args...args)&&{
try {
R2 ret = invoke( ptr.get(), std::forward<Args>(args)... );
clear();
return ret;
} catch(...) {
clear();
throw;
}
}
// invoke in the case where R is void:
template<class R2=R,
std::enable_if_t<std::is_same<R2, void>{}, int> = 0
>
R2 operator()(Args...args)&&{
try {
invoke( ptr.get(), std::forward<Args>(args)... );
clear();
} catch(...) {
clear();
throw;
}
}
// empty the fire_once:
void clear() {
invoke = nullptr;
ptr.reset();
}
// test if it is non-empty:
explicit operator bool()const{return (bool)ptr;}
// change what the fire_once contains:
template<class F, class...FArgs>
void rebind( FArgs&&... fargs ) {
clear();
auto pf = std::make_unique<F>(std::forward<FArgs>(fargs)...);
invoke = +[](void* pf, Args...args)->R {
return (*(F*)pf)(std::forward<Args>(args)...);
};
ptr = {
pf.release(),
[](void* pf){
delete (F*)(pf);
}
};
}
private:
// storage. A unique pointer with deleter
// and an invoker function pointer:
std::unique_ptr<void, void(*)(void*)> ptr{nullptr, +[](void*){}};
void(*invoke)(void*, Args...) = nullptr;
};
die sogar nicht verschiebbare Typen über die unterstützt emplace_as<T>
Schild.
Live-Beispiel.
Beachten Sie, dass Sie bewerten müssen ()
in einem rvalue-Kontext (d. h. nach a std::move
), als stille destruktive ()
schien unhöflich.
Diese Implementierung verwendet kein SBO, denn wenn dies der Fall wäre, würde es erfordern, dass der gespeicherte Typ verschiebbar ist, und es wäre (für mich) mehr Arbeit zu booten.
Hier ist eine einfachere Lösung:
auto pi = std::make_unique<int>(0);
auto ppi = std::make_shared<std::unique_ptr<int>>(std::move(pi));
std::function<void()> bar = [ppi] {
**ppi = 5;
std::cout << **ppi << std::endl;
};
Live-Beispiel hier
Du kannst nicht.
std::function
erfordert das Funktionsobjekt beCopyConstructible
.– TC
21. August 2014 um 8:23 Uhr
Dies ist sehr ähnlich zu stackoverflow.com/questions/25330716/… Warum auch nicht einfach Vorlagen für die Funktionen verwenden, anstatt
std::function
Typ löschen? Verwendenstd::function
als universeller Funktionstyp ist keine gute Idee.– Manu343726
21. August 2014 um 8:27 Uhr
Nun, die Idee war, zu vermeiden, dass die Kompilierungszeiten zu lang werden, kombiniert mit der Tatsache, dass die Leistungseinbuße für die Verwendung von
std::function
ist im Kontext von UI-Callbacks akzeptabel. (Vielleicht voreilige Optimierung!)– seertaak
21. August 2014 um 8:31 Uhr
Verwandte Frage: Nur-Verschieben-Version von std::function
– maxschlepzig
14. Oktober 2018 um 18:54 Uhr
Die jetzt im Standard enthaltene Range-Bibliothek behebt dieses Problem (nur hier aus Gründen der Abwärtskompatibilität) mit einem semiregulären Wrapper: eel.is/c++draft/range.semi.wrap
– Oliv
13. Dezember 2018 um 12:03 Uhr