Ich lerne gerade C ++ und versuche, schlechte Gewohnheiten zu vermeiden. Soweit ich weiß, enthält clang-tidy viele "Best Practices" und ich versuche, mich so gut wie möglich an sie zu halten (obwohl ich nicht unbedingt verstehe, warum sie noch als gut gelten), aber ich bin mir nicht sicher, ob ich es tue Verstehe, was hier empfohlen wird.
Ich habe diese Klasse aus dem Tutorial verwendet:
class Creature
{
private:
std::string m_name;
public:
Creature(const std::string &name)
: m_name{name}
{
}
};
Dies führt zu einem Vorschlag von clang-tidy, dass ich Wert anstelle von Referenz und Verwendung übergeben soll std::move
. Wenn ich das tue, erhalte ich den Vorschlag zu machen , name
eine Referenz (um sicherzustellen , dass nicht jedes Mal kopiert bekommt) und die Warnung , dass std::move
keine Auswirkungen haben , da name
eine ist , const
damit ich es entfernen sollte.
Die einzige Möglichkeit, keine Warnung zu erhalten, besteht darin, sie const
vollständig zu entfernen :
Creature(std::string name)
: m_name{std::move(name)}
{
}
Was logisch erscheint, da der einzige Vorteil darin const
bestand, das Durcheinander mit der ursprünglichen Zeichenfolge zu verhindern (was nicht passiert, weil ich den Wert übergeben habe). Aber ich habe auf CPlusPlus.com gelesen :
Beachten Sie jedoch, dass das Verschieben in der Standardbibliothek impliziert, dass das verschobene Objekt in einem gültigen, aber nicht angegebenen Zustand belassen wird. Dies bedeutet, dass nach einer solchen Operation der Wert des verschobenen Objekts nur zerstört oder ein neuer Wert zugewiesen werden sollte. Wenn Sie andernfalls darauf zugreifen, erhalten Sie einen nicht angegebenen Wert.
Stellen Sie sich nun diesen Code vor:
std::string nameString("Alex");
Creature c(nameString);
Da nameString
der Wert übergeben wird, std::move
wird er nur name
innerhalb des Konstruktors ungültig und berührt nicht die ursprüngliche Zeichenfolge. Aber was sind die Vorteile davon? Es scheint, als würde der Inhalt sowieso nur einmal kopiert - wenn ich beim Aufrufen als Referenz übergebe m_name{name}
, wenn ich beim Übergeben den Wert übergebe (und dann wird er verschoben). Ich verstehe, dass dies besser ist, als den Wert zu übergeben und nicht zu verwenden std::move
(weil es zweimal kopiert wird).
Also zwei Fragen:
- Habe ich richtig verstanden, was hier passiert?
- Gibt es einen Vorteil
std::move
, wenn Sie das Übergeben als Referenz verwenden und nur anrufenm_name{name}
?
Creature c("John");
macht eine zusätzliche Kopiestd::string_view
und SSO.clang-tidy
eine großartige Möglichkeit ist, mich auf Kosten der Lesbarkeit von unnötigen Mikrooptimierungen besessen zu machen. Die Frage, die hier vor allem gestellt werden muss, ist, wie oft wir den Konstruktor tatsächlich aufrufenCreature
.Antworten:
Ja.
Eine leicht zu erfassende Funktionssignatur ohne zusätzliche Überlastungen. Die Signatur zeigt sofort, dass das Argument kopiert wird - dies erspart Anrufern die Frage, ob eine
const std::string&
Referenz als Datenelement gespeichert werden könnte, und wird möglicherweise später zu einer baumelnden Referenz. Und es gibt keine Notwendigkeit , um eine Überlastung aufstd::string&& name
undconst std::string&
Argumente unnötige Kopien zu vermeiden , wenn rvalues an die Funktion übergeben werden. Einen Wert übergebenDie Funktion, die ihr Argument nach Wert nimmt, verursacht eine Kopie und eine Verschiebungskonstruktion. Übergabe eines rWertes an dieselbe Funktion
verursacht zwei Bewegungskonstruktionen. Im Gegensatz dazu gibt es beim Funktionsparameter
const std::string&
immer eine Kopie, auch wenn ein rvalue-Argument übergeben wird. Dies ist eindeutig ein Vorteil, solange der Argumenttyp billig zu verschieben ist (dies ist der Fall fürstd::string
).Es ist jedoch ein Nachteil zu berücksichtigen: Die Argumentation funktioniert nicht für Funktionen, die das Funktionsargument einer anderen Variablen zuweisen (anstatt es zu initialisieren):
führt zu einer Freigabe der Ressource,
m_name
auf die verwiesen wird, bevor sie neu zugewiesen wird. Ich empfehle, Punkt 41 in Effective Modern C ++ und auch diese Frage zu lesen .quelle
move
, wird der Speicherplatz freigegeben. Wenn ich nicht verwendemove
, wird die Zuordnung nur freigegeben, wenn der zugewiesene Speicherplatz zu klein ist, um die neue Zeichenfolge aufzunehmen, was zu einer verbesserten Leistung führt. Ist das korrekt?m_name
aus einemconst std::string&
Parameter wird der interne Speicher so lange wiederverwendet, wie erm_name
passt. Bei der Zuweisung von Verschiebungenm_name
muss der Speicher zuvor freigegeben werden. Andernfalls war es unmöglich, die Ressourcen von der rechten Seite der Aufgabe zu "stehlen".Eine bestandene lvalue bindet
name
, wird dann kopiert inm_name
.Eine bestandene rvalue bindet
name
, wird dann kopiert inm_name
.Eine bestandene lvalue wird kopiert in
name
, dann wird bewegt inm_name
.A geleitet rvalue wird bewegt in
name
, wird bewegt inm_name
.Ein übergebener Wert bindet an
name
, wird dann kopiert inm_name
.Ein übergebener Wert bindet an
rname
und wird dann verschoben , inm_name
.Da Verschiebungsvorgänge normalerweise schneller sind als Kopien, (1) besser als (0), wenn Sie viele Provisorien übergeben. (2) ist in Bezug auf Kopien / Verschiebungen optimal, erfordert jedoch eine Code-Wiederholung.
Die Code-Wiederholung kann mit vermieden werden perfekte Weiterleitung :
Möglicherweise möchten Sie optional einschränken
T
einschränken, um die Domäne der Typen einzuschränken, mit denen dieser Konstruktor instanziiert werden kann (wie oben gezeigt). C ++ 20 soll dies mit Concepts vereinfachen .In C ++ 17 sind pr-Werte von einer garantierten Kopierentfernung betroffen , die - falls zutreffend - die Anzahl der Kopien / Verschiebungen verringert, wenn Argumente an Funktionen übergeben werden.
quelle
Creature(const std::string &name) : m_name{std::move(name)} { }
in die (2) ?Wie Sie bestehen, ist hier nicht die einzige Variable. Was Sie bestehen, macht den großen Unterschied zwischen den beiden.
In C ++, haben wir alle Arten von Wertkategorie und dieses „Idiom“ gibt sich für Fälle , in denen Sie in einem Pass rvalue (wie
"Alex-string-literal-that-constructs-temporary-std::string"
oderstd::move(nameString)
), die Ergebnisse in 0 Kopien vonstd::string
gemacht werden (die Art auch dann nicht haben copy-konstruierbar sein für rvalue-Argumente) und verwendet nurstd::string
den Verschiebungskonstruktor.Etwas verwandte Fragen und Antworten .
quelle
Es gibt mehrere Nachteile des Pass-by-Value-and-Move-Ansatzes gegenüber der Pass-by- (rv) -Referenz:
quelle
m_name{name}
Teil, an dem er kopiert wird?std::string nameString("Alex"); Creature c(nameString);
Ein Objekt ist einnameString
anderes, ein Funktionsargument und das dritte ist ein Klassenfeld.In meinem Fall verursachte das Umschalten auf "Übergeben nach Wert" und das anschließende Ausführen eines "std: move" einen Fehler bei der Heap-Verwendung nach dem freien Zugriff in Address Sanitizer.
https://travis-ci.org/github/acgetchell/CDT-plusplus/jobs/679520360#L3165
Also habe ich es ausgeschaltet, ebenso wie der Vorschlag in Ordnung.
https://github.com/acgetchell/CDT-plusplus/compare/80c96789f0a2...0d78fd63b332
quelle