webentwicklung-frage-antwort-db.com.de

Warum können wir "std :: move" für ein "const" -Objekt verwenden?

In C++ 11 können wir diesen Code schreiben:

struct Cat {
   Cat(){}
};

const Cat cat;
std::move(cat); //this is valid in C++11

wenn ich std::move anrufe, bedeutet dies, dass ich das Objekt verschieben möchte, d. h. ich werde das Objekt ändern. Das Verschieben eines const-Objekts ist nicht sinnvoll, weshalb std::move dieses Verhalten nicht einschränkt? Es wird in Zukunft eine Falle sein, oder?

Hier bedeutet Falle, wie Brandon in dem Kommentar erwähnt hat:

"Ich denke, er meint damit, dass es ihn hinterhält," wenn er es nicht merkt.

In dem Buch 'Effective Modern C++' von Scott Meyers gibt er ein Beispiel:

class Annotation {
public:
    explicit Annotation(const std::string text)
     : value(std::move(text)) //here we want to call string(string&&),
                              //but because text is const, 
                              //the return type of std::move(text) is const std::string&&
                              //so we actually called string(const string&)
                              //it is a bug which is very hard to find out
private:
    std::string value;
};

Wenn std::move von einem const-Objekt ausgeschlossen wurde, könnten wir den Fehler leicht herausfinden, oder?

83
camino
struct strange {
  mutable size_t count = 0;
  strange( strange const&& o ):count(o.count) { o.count = 0; }
};

const strange s;
strange s2 = std::move(s);

hier sehen wir eine Verwendung von std::move auf einem T const. Es gibt einen T const&& zurück. Wir haben einen Bewegungskonstruktor für strange, der genau diesen Typ annimmt.

Und es heißt.

Es stimmt, dass dieser merkwürdige Typ seltener ist als die Fehler, die Ihr Vorschlag beheben würde.

Andererseits funktioniert der vorhandene std::move besser in generischem Code. Sie wissen nicht, ob der Typ, mit dem Sie arbeiten, ein T oder ein T const ist.

Es gibt einen Trick, den Sie übersehen, nämlich dass std::move(cat)nichts bewegt. Es weist den Compiler lediglich an, try zu verschieben. Da Ihre Klasse jedoch keinen Konstruktor hat, der einen const CAT&& akzeptiert, verwendet sie stattdessen den impliziten const CAT& copy-Konstruktor und kopiert sie sicher. Keine Gefahr, keine Falle. Wenn der Kopierkonstruktor aus irgendeinem Grund deaktiviert ist, wird ein Compiler-Fehler angezeigt.

struct CAT
{
   CAT(){}
   CAT(const CAT&) {std::cout << "COPY";}
   CAT(CAT&&) {std::cout << "MOVE";}
};

int main() {
    const CAT cat;
    CAT cat2 = std::move(cat);
}

druckt COPY, nicht MOVE.

http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f

Beachten Sie, dass der Fehler in dem von Ihnen erwähnten Code ein performance - Problem ist, kein Stabilität - Problem. Daher kann ein solcher Fehler niemals zum Absturz führen. Es wird nur eine langsamere Kopie verwendet. Darüber hinaus tritt ein solcher Fehler auch für Nicht-Konstante-Objekte auf, die über keine Bewegungskonstruktoren verfügen. Wenn Sie also nur eine const-Überladung hinzufügen, werden sie nicht alle abfangen. Wir könnten prüfen, ob die Möglichkeit besteht, Konstrukte oder Zuweisungen aus dem Parametertyp zu verschieben, aber das würde generischen Vorlagencode stören, der angeblich auf den Copy-Konstruktor zurückgreift aus const CAT&& konstruieren können, wem soll ich sagen, dass er es nicht kann?

87
Mooing Duck

Ein Grund, warum der Rest der Antworten bisher übersehen wurde, ist die Fähigkeit von generic code, sich gegenüber einer Bewegung widerstandsfähig zu machen. Zum Beispiel möchte ich sagen, dass ich eine generische Funktion schreiben wollte, die alle Elemente aus einer Art von Container verschoben hat, um eine andere Art von Container mit den gleichen Werten zu erstellen:

template <class C1, class C2>
C1
move_each(C2&& c2)
{
    return C1(std::make_move_iterator(c2.begin()),
              std::make_move_iterator(c2.end()));
}

Cool, jetzt kann ich relativ effizient einen vector<string> aus einem deque<string> erstellen und jede einzelne string wird dabei verschoben.

Aber was ist, wenn ich von einer map wechseln möchte?

int
main()
{
    std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
    auto v = move_each<std::vector<std::pair<int, std::string>>>(m);
    for (auto const& p : v)
        std::cout << "{" << p.first << ", " << p.second << "} ";
    std::cout << '\n';
}

Wenn std::move auf einem Argument ohne -const bestand, würde die obige Instanziierung von move_each nicht kompiliert werden, da versucht wird, einen const int (den key_type der map) zu verschieben. Aber dieser Code ist egal wenn er den key_type nicht verschieben kann. Sie möchte den mapped_type (std::string) aus Leistungsgründen verschieben.

Es ist für dieses Beispiel und unzählige andere Beispiele wie bei der generischen Codierung, dass std::move eine Anforderung zum Verschieben von ist, keine Anforderung zum Verschieben.

19
Howard Hinnant

Ich habe das gleiche Anliegen wie das OP.

std :: move bewegt ein Objekt nicht und garantiert auch nicht, dass das Objekt beweglich ist. Warum heißt es dann Umzug?

Ich glaube nicht beweglich zu sein kann eines der folgenden zwei Szenarien sein:

1. Der Bewegungstyp ist const.

Wir haben das Schlüsselwort const in der Sprache, weil der Compiler verhindern soll, dass ein Objekt als const definiert wird. In Anbetracht des Beispiels in Scott Meyers 'Buch:

    class Annotation {
    public:
     explicit Annotation(const std::string text)
     : value(std::move(text)) // "move" text into value; this code
     { … } // doesn't do what it seems to!    
     …
    private:
     std::string value;
    };

Was bedeutet es wörtlich? Verschiebe einen const-String zum value-Member - zumindest verstehe ich das, bevor ich die Erklärung lese.

Wenn die Sprache keine Bewegung ausführen soll oder nicht garantiert, dass die Bewegung beim Aufruf von std :: move () zutrifft, ist sie bei der Verwendung von Word-Verschiebung buchstäblich irreführend.

Wenn die Sprache Menschen mit std :: move zu einer besseren Effizienz ermutigt, muss sie so früh wie möglich Fallen wie diese vermeiden, insbesondere bei dieser Art von offensichtlichen wörtlichen Widerspruch.

Ich bin damit einverstanden, dass die Leute wissen sollten, dass das Verschieben einer Konstante unmöglich ist, aber diese Verpflichtung sollte nicht bedeuten, dass der Compiler schweigen kann, wenn offensichtliche Widersprüche auftreten.

2. Das Objekt hat keinen Bewegungskonstruktor

Ich persönlich denke, dass dies eine von OPs Sorge getrennte Geschichte ist, wie Chris Drew sagte

@hvd Das scheint mir ein bisschen ein Nichtargument zu sein. Nur weil der Vorschlag von OP nicht alle Fehler in der Welt behebt, heißt das nicht unbedingt, dass dies eine schlechte Idee ist (wahrscheinlich, aber nicht aus dem Grund, den Sie angeben). - Chris Drew

2
Bogeegee

Ich bin überrascht, dass niemand den Aspekt der Rückwärtskompatibilität erwähnt hat. Ich glaube, std::move wurde absichtlich dafür entwickelt, dies in C++ 11 zu tun. Stellen Sie sich vor, Sie arbeiten mit einer älteren Codebase, die stark auf C++ 98-Bibliotheken angewiesen ist. Ohne den Rückfall auf die Zuweisung von Kopien würde das Bewegen die Dinge zerstören. 

0
mlo