8 Stimmen

c++11 optimale parameterübergabe

Betrachten Sie diese Klassen:

#include <iostream>
#include <string>

class A
{
    std::string test;
public:
    A (std::string t) : test(std::move(t)) {}
    A (const A & other) { *this = other; }
    A (A && other) { *this = std::move(other); }

    A & operator = (const A & other)
    {
        std::cerr<<"copying A"<<std::endl;
        test = other.test;
        return *this;
    }

    A & operator = (A && other)
    {
        std::cerr<<"move A"<<std::endl;
        test = other.test;
        return *this;
    }
};

class B
{
    A a;
public:   
    B (A && a) : a(std::move(a)) {}
    B (A const & a) : a(a) {}
};

Bei der Erstellung einer B habe ich immer einen optimalen Vorwärtspfad für A ein Zug für rvalues oder eine Kopie für lvalues.

Ist es möglich, das gleiche Ergebnis mit einem Konstruktor zu erreichen? Das ist in diesem Fall kein großes Problem, aber was ist mit mehreren Parametern? Ich bräuchte Kombinationen aus allen möglichen Vorkommen von lvalues und rvalues in der Parameterliste.

Dies ist nicht auf Konstruktoren beschränkt, sondern gilt auch für Funktionsparameter (z. B. Setter).

Hinweis: Diese Frage bezieht sich ausschließlich auf class B ; class A dient nur zur Veranschaulichung, wie die Kopier-/Verschiebeaufrufe ausgeführt werden.

9voto

Howard Hinnant Punkte 191035

Der "by-value"-Ansatz ist eine Option. Er ist nicht so optimal wie Ihr Ansatz, erfordert aber nur eine Überladung:

class B
{
    A a;
public:   
    B (A _a) : a(move(_a)) {}
};

Der Preis ist 1 zusätzlicher Zug für die Konstruktion von lvalues und xvalues, aber dies ist immer noch optimal für prvalues (1 Zug). Ein "xvalue" ist ein lvalue, der mit std::move in einen rvalue umgewandelt wurde.

Sie könnten auch eine "perfekte Weiterleitung" versuchen:

class B
{
    A a;
public:   
    template <class T,
              class = typename std::enable_if
              <
                 std::is_constructible<A, T>::value
              >::type>
    B (T&& _a) : a(std::forward<T>(_a)) {}
};

Damit erreichen Sie wieder die optimale Anzahl von Kopier-/Verschiebekonstruktionen. Allerdings sollten Sie den Template-Konstruktor so einschränken, dass er nicht zu generisch ist. Sie könnten es vorziehen, is_convertible anstelle von is_constructible zu verwenden, wie ich es oben getan habe. Dies ist auch eine Lösung mit einem einzigen Konstruktor, aber je mehr Parameter Sie hinzufügen, desto komplizierter wird Ihre Einschränkung.

Hinweis : Der Grund, warum die obige Einschränkung notwendig ist, ist, dass ohne sie die Kunden von B die falsche Antwort erhalten, wenn sie die std::is_constructible<B, their_type>::value . Sie wird fälschlicherweise wahr antworten, wenn keine angemessene Einschränkung für B .

Ich würde sagen, dass keine dieser Lösungen immer besser ist als die anderen. Es gibt hier technische Kompromisse, die man eingehen muss.

2voto

Kerrek SB Punkte 445528

Verwenden Sie einen abgeleiteten Parametertyp für den Konstruktor für B :

template <typename T> explicit B(T && x) : a(std::forward<T>(x) { }

Dies funktioniert für jedes Argument, von dem ein A Objekt konstruierbar ist.

Si A mehrere Konstruktoren mit einer unterschiedlichen Anzahl von Argumenten hat, können Sie das Ganze einfach variabel machen, indem Sie ... überall.

Wie @Howard sagt, sollten Sie jedoch eine Einschränkung hinzufügen, damit die Klasse nicht den Anschein erweckt, aus Argumenten konstruierbar zu sein, aus denen sie in Wirklichkeit nicht besteht.

1voto

Emilio Garavaglia Punkte 19399

Wenn die string in Ihrer Probe ist std::string Die standardmäßig zur Verfügung gestellten Kopier- und Verschiebefunktionen rufen ihre jeweiligen In-Members auf. Und std::string hat sowohl copy als auch move implementiert, so dass temporäre Elemente verschoben und Variablen kopiert werden.

Es besteht keine Notwendigkeit, einen speziellen Kopier- und Verschiebe-Ctor zu definieren und zuzuweisen. Sie können einfach mit dem Konstruktor

A::A(string s) :test(std::move(s)) {}

Im Allgemeinen kann eine unkomplizierte Implementierung von Kopieren und Verschieben wie folgt aussehen

class A
{
public:
    A() :p() {}

    A(const A& a) :p(new data(*a.p)) {} //copy
    A(A&& a) :p(a.p) { a.p=0; }         //move

    A& operator=(A a) //note: pass by value
    { clear(); swap(a); return *this; }
    ~A() { clear(); }

    void swap(A& a) { std::swap(p,a.p); }
    void clear() { delete p; p=0; }

private:

    data* p;
};

En operator= nimmt einen Wert an, der intern verschoben wird. Wenn er aus einem temporären Wert stammt, wird er verschoben, wenn er aus einer Variablen stammt, wird er kopiert. Der Unterschied zwischen Kopieren und Verschieben erfordert unterschiedliche Konstruktoren, aber wenn wir A ableiten als

class B: public A
{
...
};

Es besteht keine Notwendigkeit, irgendetwas zu überschreiben, da der Standard-Kopiersektor für B die Kopie für A aufruft, und die Standard-Verschiebung für B ruft die Verschiebung für A auf, und alle Standard-Zuweisungsoperatoren für B rufen den einzigen für A definierten auf (der verschiebt oder kopiert, je nachdem, was weitergegeben wurde).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X