Move-only-Version von std::function

Lesezeit: 10 Minuten

Move only Version von stdfunction
Orm

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?

  • 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 von function ist kopierbar.

    – dyp

    15. August 14 um 19:34 Uhr


  • Ja, du kannst Wiederverwendung std::function damit es funktioniert.. irgendwie (beachten Sie das function_mo selbst ist nur bewegungsfähig, also keine Ausnahmen von hack 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öchten function<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


1643914207 890 Move only Version von stdfunction
Yakk – Adam Nevraumont

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).

  • Ihr Vorschlag, den Code “unmöglich schnelle Delegierte” zu verwenden, ist ein Ablenkungsmanöver: Der wesentliche Trick, mit dem er die Dinge beschleunigt, ist spezifisch für Methodenzeiger (und aus den Kommentaren geht hervor, dass er die Dinge sowieso nicht wirklich beschleunigt). Wie geschrieben, funktioniert es nicht mit benutzerdefinierten Lambdas (oder anderen Funktoren), worum es in der ursprünglichen Frage ging. Ihr Beispielcode scheint eher wie ein herkömmlicher zu sein std::function Implementierung.

    – Arthur Tacca

    14. Juni 18 um 08:41 Uhr

  • @arthur Der Delegiertencode kann leicht geändert werden, um ihn zu konsumieren operator() meiner Erfahrung nach implizit (Ignorieren von Vorlagenfällen). Ich weiß nicht, ob es noch einen Leistungsschub gibt; Es ist lange her, dass ich es in anderen Situationen als in Legacy-Situationen verwendet habe (es ist zu mühsam, es zu warten).

    – Yakk – Adam Nevraumont

    14. Juni 18 um 11:20 Uhr

  • Was meinst du mit “verbrauchen operator()“? Meinst du willkürliche Klassen verbrauchen, die eine haben operator() (einschließlich Lambda-Funktionen)? Wenn dies der Fall ist, ist dies der größte Aufwand beim Schreiben eines typgelöschten Funktors. Egal, ob Sie es für schwierig oder einfach halten, es macht keinen Sinn, etwas zu verlinken, das dies nicht tut (insbesondere etwas, das zusätzliche Anstrengungen aufwendet, um etwas zu tun, das nichts damit zu tun hat).

    – Arthur Tacca

    14. Juni 18 um 11:34 Uhr

  • @arthur es ist ein Beispiel für eine Std-Funktion mit Nullzuweisung wie; meine ist vollständiger, weist aber zu.

    – Yakk – Adam Nevraumont

    14. Juni 18 um 12:16 Uhr

Move only Version von stdfunction
Unbeteiligter Zeuge

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.

  • öffentliche Basis ermöglicht Konvertierung in const std :: Funktion & und Kopieren (mit Ausnahme)

    – Kumpel

    4. November 18 um 19:32 Uhr

  • @pal Umwandlung in std::function ist so komisch (?). Auf diese Weise können wir später kopieren std::function<void()> ff = f3; auto fff = ff; Wie ist das möglich?

    – Gabriel

    22. April 2020 um 19:53 Uhr

  • Diese Implementierung ist etwas fehlerhaft: unique_function(Fn&& f) passt zu viel finde ich… fn a; fn b; a = b; kompiliert, sollte aber nicht …

    – Gabriel

    22. April 2020 um 20:03 Uhr

  • Ich habe diese Vorlage einfach für deaktiviert unique_function<T> selbst.

    – WolleTD

    15. November 20 um 15:36 Uhr


  • Sollte die Möglichkeit entfernen, einen Verweis auf das Callable zu speichern unique_function(Fn&& f) : base(wrapper<std::remove_reference_t<Fn>>{ std::forward<std::remove_reference_t<Fn>>(f) }) { } und dasselbe für operator=

    – Ricky Lunge

    4. März 21 um 3:39 Uhr


1643914208 173 Move only Version von stdfunction
mskfisher

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.

.

758860cookie-checkMove-only-Version von std::function

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy