Frage 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 anrufe std::movebedeutet, dass ich das Objekt verschieben möchte, d. h. ich werde das Objekt ändern. Um einen zu bewegen const Objekt ist unvernünftig, also warum std::move schränkt dieses Verhalten nicht ein? Es wird eine Falle in der Zukunft sein, oder?

Hier bedeutet Trap wie Brandon im Kommentar erwähnt:

"Ich denke, er meint, dass es ihn hinterhältig" hinterlistig "macht, wenn er es nicht tut   Er weiß, dass er eine Kopie hat, die nicht seine Absicht ist. "

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;
};

Ob std::move war verboten, auf einem zu operieren const Objekt, wir könnten den Fehler leicht herausfinden, oder?


76
2018-02-18 22:22


Ursprung


Antworten:


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 einen T const. Es gibt a zurück T const&&. Wir haben einen Move-Konstruktor für strange das nimmt genau diesen Typ an.

Und es heißt.

Nun ist es wahr, dass dieser seltsame Typ seltener ist als die Fehler, die Ihr Vorschlag beheben würde.

Aber auf der anderen Seite, das Bestehende std::move funktioniert besser in generischem Code, wo Sie nicht wissen, ob der Typ, mit dem Sie arbeiten, ein ist T oder ein T const.


40
2018-02-18 22:44



Es gibt einen Trick hier, den Sie übersehen, nämlich dass std::move(cat)  bewegt eigentlich nichts. Es teilt dem Compiler lediglich mit Versuchen bewegen. Da Ihre Klasse jedoch keinen Konstruktor hat, der ein akzeptiert const CAT&&Es wird stattdessen das Implizite verwendet const CAT& Konstruktor kopieren und sicher kopieren. Keine Gefahr, keine Falle. Wenn der Kopierkonstruktor aus irgendeinem Grund deaktiviert ist, erhalten Sie einen Compilerfehler.

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

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

Drucke COPYnicht MOVE.

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

Beachten Sie, dass der Fehler in dem Code, den Sie erwähnen, ist a Performance Ausgabe, nicht a Stabilität Problem, so ein Bug wird nie einen Absturz verursachen. Es wird nur eine langsamere Kopie verwenden. Darüber hinaus tritt ein solcher Fehler auch für nicht-konstante Objekte auf, die keine Move-Konstruktoren haben, also einfach ein hinzufügen const Überlastung wird nicht alle von ihnen erfassen. Wir könnten nach der Fähigkeit suchen, die Konstrukt- oder Bewegungszuweisung vom Parametertyp zu verschieben, aber dies würde den generischen Vorlagencode stören soll auf den Kopierkonstruktor zurückgreifen. Und hey, vielleicht möchte jemand daraus konstruieren können const CAT&&Wer soll ich sagen, er kann nicht?


81
2018-02-18 22:28



Ein Grund, warum der Rest der Antworten bisher übersehen wurde, ist die Fähigkeit für generisch Code, um angesichts der Bewegung widerstandsfähig zu sein. Nehmen wir zum Beispiel an, dass ich eine generische Funktion schreiben wollte, die alle Elemente aus einem Containertyp verschiebt, um einen anderen Containertyp mit denselben 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 erstellen vector<string> von einem deque<string> und jedes Individuum string wird dabei bewegt.

Aber was ist, wenn ich von einem bewegen will? map?

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';
}

Ob std::move bestand auf einer nichtconst Argument, die obige Instantiierung von move_each würde nicht kompilieren, weil es versucht, einen zu verschieben const int(das key_type des map). Aber dieser Code ist mir egal wenn es sich nicht bewegen kann key_type. Es will die bewegen mapped_type (std::string) aus Leistungsgründen.

Es ist für dieses Beispiel und zahllose andere Beispiele wie es in der generischen Codierung ist std::move ist ein Bitte um Umzugkeine Aufforderung, sich zu bewegen.


16
2018-02-19 00:10



Ich habe die gleiche Sorge wie das OP.

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

Ich denke, dass nicht beweglich sein kann, kann eines der folgenden zwei Szenarien sein:

1. Der Bewegungstyp ist const.

Der Grund, warum wir das Schlüsselwort const in der Sprache haben, ist, dass der Compiler jede Änderung an einem als const definierten Objekt verhindern soll. Angesichts 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 eine const-Zeichenkette auf den Wert member - zumindest verstehe ich das, bevor ich die Erklärung lese.

Wenn die Sprache beabsichtigt, nicht zu verschieben oder nicht zu garantieren, ist move anwendbar, wenn std :: move () aufgerufen wird, dann ist es buchstäblich irreführend, wenn das Wort move verwendet wird.

Wenn die Sprache Leute ermutigt, std :: move einzusetzen, um eine bessere Effizienz zu erreichen, muss sie solche Fallen so früh wie möglich verhindern, besonders für diese Art von offensichtlichen wörtlichen Widersprüchen.

Ich stimme zu, dass Menschen sich bewusst sein sollten, dass das Bewegen einer Konstante unmöglich ist, aber diese Verpflichtung sollte nicht implizieren, dass der Compiler still sein kann, wenn ein offensichtlicher Widerspruch auftritt.

2. Das Objekt hat keinen Bewegungskonstruktor

Persönlich denke ich, dass dies eine separate Geschichte von der Sorge von OP ist, wie Chris Drew sagte

@ hvd Das scheint für mich ein bisschen ein Argument zu sein. Nur weil der Vorschlag von OP nicht alle Fehler in der Welt beseitigt, bedeutet das nicht notwendigerweise, dass es eine schlechte Idee ist (wahrscheinlich, aber nicht aus dem Grund, den Sie geben). - Chris Drew


1
2017-08-26 20:08