'Flexibles' move bzw. copy von Variablen bei Konstruktor-Aufruf



  • Hallo zusammen,

    ich habe eine Klasse deren Konstruktor zwei Variablen übergeben kriegt.
    Falls die Variablen noch benötigt werden, soll der Copy-Constructor aufgerufen werden, sonst der Move-Constructor.

    #include <iostream>
    
    struct Foo{
    	Foo(){}
    	Foo(const Foo&) { std::cout << "copy Foo" << std::endl;}
    	Foo(Foo&&) { std::cout << "move Foo" << std::endl;}
    };
    
    struct Bar{
    	Bar(){}
    	Bar(const Bar&) { std::cout << "copy Bar" << std::endl;}
    	Bar(Bar&&) { std::cout << "move Bar" << std::endl;}
    };
    
    struct FooBar {
    	FooBar(const Foo& foo, const Bar& bar) : _foo(foo), _bar(bar) {}
    	FooBar(Foo&& foo, const Bar& bar) : _foo(std::move(foo)), _bar(bar) {}
    	FooBar(const Foo& foo, Bar&& bar) : _foo(foo), _bar(std::move(bar)) {}
    	FooBar(Foo&& foo, Bar&& bar) : _foo(std::move(foo)), _bar(std::move(bar)) {}
    
    	Foo _foo;
    	Bar _bar;
    };
    
    int main(void) {
    	Foo foo1;
    	Foo foo2;
    	Bar bar1;
    	Bar bar2;
    	FooBar fooBar(foo1, bar1);
    	FooBar fooBar1(std::move(foo1), bar1);
    	FooBar fooBar2(foo2, std::move(bar1));
    	FooBar fooBar3(std::move(foo2), std::move(bar2));
    }
    

    copy Foo
    copy Bar
    move Foo
    copy Bar
    copy Foo
    move Bar
    move Foo
    move Bar

    Mit dieser Version brauch ich dafür immer (bezüglich Variablen-Anzahl) quadratisch viele Konstruktoren? Bei 2 Variablen ist es ja noch ok, aber bei 3 wirds schon nervig.

    Kann man die Anzahl der Konstruktoren irgendwie reduzieren?

    Gruß,
    XSpille



  • XSpille schrieb:

    Mit dieser Version brauch ich dafür immer (bezüglich Variablen-Anzahl) quadratisch viele Konstruktoren? Bei 2 Variablen ist es ja noch ok, aber bei 3 wirds schon nervig.

    Quadratisch? Du brauchst 2n.

    Meiner Erfahrung nach ist maximal ein Parameter move-Bedürftig, also war das bei mir noch nie ein Problem. Spielst du nur rum oder hast du das Problem tatsächlich?

    In Fällen wo die Geschwindigkeit keine Rolle spielt kannst du einfach immer die const&-Variante nehmen. Damit bist du noch genug flexibel, da neue &&-Überladungen nichts am Interface ändern.



  • Du kannst stumpf alles per Value erwarten und dann moven. Dann hast du entweder copy+move oder move+move, wenn du davon ausgehst dass die move-Kosten quasi gegen 0 gehen, ist das auch egal.



  • reduktionskost schrieb:

    XSpille schrieb:

    Mit dieser Version brauch ich dafür immer (bezüglich Variablen-Anzahl) quadratisch viele Konstruktoren? Bei 2 Variablen ist es ja noch ok, aber bei 3 wirds schon nervig.

    Quadratisch? Du brauchst 2n.

    Meiner Erfahrung nach ist maximal ein Parameter move-Bedürftig, also war das bei mir noch nie ein Problem. Spielst du nur rum oder hast du das Problem tatsächlich?

    In Fällen wo die Geschwindigkeit keine Rolle spielt kannst du einfach immer die const&-Variante nehmen. Damit bist du noch genug flexibel, da neue &&-Überladungen nichts am Interface ändern.

    Du hast natürlich recht... Ist ja sogar exponentiell...

    Ich schreibe eine Template-Library, wo ich mir vorstellen könnte, dass beide Parameter zum Moven in Frage kommen können.
    Und deswegen habe ich es mir eher theoretisch überlegt, was man in so einem Fall machen kann, wenn er mehr Parameter würden.



  • cooky451 schrieb:

    Du kannst stumpf alles per Value erwarten und dann moven. Dann hast du entweder copy+move oder move+move, wenn du davon ausgehst dass die move-Kosten quasi gegen 0 gehen, ist das auch egal.

    Genau das.



  • cooky451 schrieb:

    Du kannst stumpf alles per Value erwarten und dann moven. Dann hast du entweder copy+move oder move+move, wenn du davon ausgehst dass die move-Kosten quasi gegen 0 gehen, ist das auch egal.

    👍



  • Du kannst stumpf alles per Value erwarten und dann moven. Dann hast du entweder copy+move oder move+move, wenn du davon ausgehst dass die move-Kosten quasi gegen 0 gehen, ist das auch egal.

    Ich möchte auch ausdrücken dass ich das so gemacht hätte, da ich wichtig bin.


  • Mod

    #include <iostream>
    #include <type_traits>
    #include <utility>
    
    struct Foo{
        Foo(){}
        Foo(const Foo&) { std::cout << "copy Foo" << std::endl;}
        Foo(Foo&&) { std::cout << "move Foo" << std::endl;}
    };
    
    struct Bar{
        Bar(){}
        Bar(const Bar&) { std::cout << "copy Bar" << std::endl;}
        Bar(Bar&&) { std::cout << "move Bar" << std::endl;}
    };
    
    struct FooBar {
        template <typename T, typename U,
                  typename = typename std::enable_if<std::is_convertible<T, Foo>::value
                                                  && std::is_convertible<U, Bar>::value>::type>
        FooBar(T&& foo, U&& bar) : _foo(std::forward<T>(foo)), _bar(std::forward<U>(bar)) {}
    
        Foo _foo;
        Bar _bar;
    };
    
    int main(void) {
        Foo foo1;
        Foo foo2;
        Bar bar1;
        Bar bar2;
        FooBar fooBar(foo1, bar1);
        FooBar fooBar1(std::move(foo1), bar1);
        FooBar fooBar2(foo2, std::move(bar1));
        FooBar fooBar3(std::move(foo2), std::move(bar2));
    }
    


  • Danke camper!

    Ich finds geil 👍

    Allerdings verstehe ich noch nicht, warum es funktioniert.
    Wieso passen denn die Parameter ohne std::move für die erwartete rvalue-Referenz?



  • XSpille schrieb:

    Danke camper!

    Ich finds geil 👍

    Allerdings verstehe ich noch nicht, warum es funktioniert.
    Wieso passen denn die Parameter ohne std::move für die erwartete rvalue-Referenz?

    Stichwort reference collapsing. Der Parameter wird dann z.B. als Foo& erkannt, zusammen gibt das dann Foo& &&, was wieder zu Foo& geschrumpft wird. Übergibst du ein rvalue, ist der Typ Foo&& und der gesamte Parameter Foo&& &&, was zu Foo&& geschrumpft wird. Stichwort perfect forwarding.



  • reference collapsing ist genau das Stichwort, das ich brauchte 🙂

    thx 👍


Anmelden zum Antworten