Haben rWert-Verweise auf const irgendeinen Nutzen?

Antworten:

78

Sie sind gelegentlich nützlich. Der Entwurf C ++ 0x selbst verwendet sie an einigen Stellen, zum Beispiel:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

Die beiden oben genannten Überladungen stellen sicher, dass die anderen ref(T&)und cref(const T&)Funktionen nicht an r-Werte gebunden sind (was sonst möglich wäre).

Aktualisieren

Ich habe gerade den offiziellen Standard N3290 überprüft , der leider nicht öffentlich verfügbar ist und in 20.8 Funktionsobjekten [function.objects] / p2 enthalten ist:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

Dann überprüfte ich den neuesten öffentlich zugänglichen Entwurf nach C ++ 11, N3485 , und in 20.8 Funktionsobjekten [function.objects] / p2 heißt es immer noch:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
Howard Hinnant
quelle
Betrachtet man cppreference, so scheint dies nicht mehr der Fall zu sein. Irgendwelche Ideen warum? Werden andere Orte const T&&verwendet?
Pubby
56
Warum haben Sie dreimal denselben Code in Ihre Antwort aufgenommen? Ich habe zu lange versucht, einen Unterschied zu finden.
typ1232
9
@ typ1232: Es scheint, dass ich die Antwort fast 2 Jahre nach der Beantwortung aktualisiert habe, da in den Kommentaren Bedenken bestehen, dass die referenzierten Funktionen nicht mehr angezeigt werden. Ich habe ein Kopieren / Einfügen von N3290 und vom damals letzten Entwurf N3485 durchgeführt, um zu zeigen, dass die Funktionen immer noch angezeigt wurden. Die Verwendung von Kopieren / Einfügen war meiner Meinung nach der beste Weg, um sicherzustellen, dass mehr Augen als meine bestätigen konnten, dass ich eine geringfügige Änderung dieser Signaturen nicht übersah.
Howard Hinnant
1
@kevinarpe: Die "anderen Überladungen" (hier nicht gezeigt, aber im Standard) nehmen Wertreferenzen an und werden nicht gelöscht. Die hier gezeigten Überladungen passen besser zu rWerten als die Überladungen, die lWertreferenzen annehmen. Rvalue-Argumente binden also an die hier gezeigten Überladungen und verursachen dann einen Fehler bei der Kompilierung, da diese Überladungen gelöscht werden.
Howard Hinnant
1
Die Verwendung von const T&&verhindert, dass jemand törichterweise explizite Vorlagenargumente des Formulars verwendet ref<const A&>(...). Das ist kein besonders starkes Argument, aber die Kosten für const T&&Over T&&sind ziemlich gering.
Howard Hinnant
6

Die Semantik des Erhaltens einer konstanten Wertreferenz (und nicht für =delete) besteht darin, zu sagen:

  • Wir unterstützen die Operation für Werte nicht!
  • obwohl wir immer noch kopieren , weil wir die übergebene Ressource nicht verschieben können oder weil es keine tatsächliche Bedeutung für das "Verschieben" gibt.

Der folgende Anwendungsfall könnte meiner Meinung nach ein guter Anwendungsfall für den r-Wert-Verweis auf const gewesen sein , obwohl die Sprache beschlossen hat, diesen Ansatz nicht zu wählen (siehe Original-SO-Beitrag ).


Der Fall: Konstruktor für intelligente Zeiger aus Rohzeiger

Es wäre normalerweise ratsam, make_uniqueund zu verwenden make_shared, aber beides unique_ptrund shared_ptrkann aus einem rohen Zeiger konstruiert werden. Beide Konstruktoren erhalten den Zeiger nach Wert und kopieren ihn. Beide erlauben (dh im Sinne von: nicht verhindern ) eine fortgesetzte Verwendung des ursprünglichen Zeigers, der im Konstruktor an sie übergeben wurde.

Der folgende Code wird kompiliert und führt zu double free :

int* ptr = new int(9);
std::unique_ptr<int> p { ptr };
// we forgot that ptr is already being managed
delete ptr;

Sowohl unique_ptrund shared_ptrkönnte verhindern , dass die oben , wenn ihre entsprechenden Konstrukteure erwarten würde den Rohzeiger zu bekommen als const rvalue , zB für unique_ptr:

unique_ptr(T* const&& p) : ptr{p} {}

In diesem Fall würde der oben genannte doppelte freie Code nicht kompiliert, aber der folgende würde:

std::unique_ptr<int> p1 { std::move(ptr) }; // more verbose: user moves ownership
std::unique_ptr<int> p2 { new int(7) };     // ok, rvalue

Beachten Sie, dass ptrdies auch nach dem Verschieben noch verwendet werden kann, sodass der potenzielle Fehler nicht vollständig behoben ist. Wenn der Benutzer jedoch einen std::movesolchen Fehler aufrufen muss, fällt dies unter die allgemeine Regel: Verwenden Sie keine Ressource, die verschoben wurde.


Man kann fragen: OK, aber warum T* const&& p ?

Der Grund ist einfach, um die Erstellung eines unique_ptr konstanten Zeigers zu ermöglichen . Denken Sie daran , dass const rvalue Referenz ist allgemeinerer als nur rvalue Referenz , da sie sowohl akzeptiert constund non-const. Wir können also Folgendes zulassen:

int* const ptr = new int(9);
auto p = std::unique_ptr<int> { std::move(ptr) };

dies würde nicht gehen , wenn wir nur erwarten rvalue Referenz (Kompilierung - Fehler: kann nicht binden const rvalue zu rvalue ).


Jedenfalls ist dies zu spät, um so etwas vorzuschlagen. Diese Idee bietet jedoch eine vernünftige Verwendung eines r-Wert-Verweises auf const .

Amir Kirsh
quelle
Ich war unter Leuten mit ähnlichen Ideen, aber ich hatte nicht die Unterstützung, um voranzukommen.
Red.Wave
"auto p = std :: unique_ptr {std :: move (ptr)};" Kompiliert nicht mit dem Fehler "Abzug der Klassenvorlagenargumente fehlgeschlagen". Ich denke, es sollte "unique_ptr <int>" sein.
Zehui Lin
4

Sie sind zulässig und sogar nach Funktionen geordnet. constDa Sie sich jedoch nicht von dem angegebenen const-Objekt entfernen können const Foo&&, sind sie nicht nützlich.

Gene Bushuyev
quelle
Was genau meinst du mit der "Rang" Bemerkung? Hat das etwas mit Überlastungsauflösung zu tun?
Fredoverflow
Warum konnten Sie nicht von einem const rvalue-ref wechseln, wenn der angegebene Typ einen Verschiebungsfaktor hat, der einen const rvalue-ref akzeptiert?
Fred Nurk
6
@FredOverflow, die Rangfolge der Überlastung ist folgende:const T&, T&, const T&&, T&&
Gene Bushuyev
2
@Fred: Wie bewegst du dich, ohne die Quelle zu ändern?
Fredoverflow
3
@Fred: Veränderbare Datenelemente oder das Verschieben für diesen hypothetischen Typ erfordern keine Änderung der Datenelemente.
Fred Nurk
2

Neben std :: ref verwendet die Standardbibliothek für denselben Zweck auch die Konstantenwertreferenz in std :: as_const .

template <class T>
void as_const(const T&&) = delete;

Es wird auch als Rückgabewert in std :: optional verwendet, wenn der umschlossene Wert abgerufen wird:

constexpr const T&& operator*() const&&;
constexpr const T&& value() const &&;

Sowie in std :: get :

template <class T, class... Types>
constexpr const T&& get(const std::variant<Types...>&& v);
template< class T, class... Types >
constexpr const T&& get(const tuple<Types...>&& t) noexcept;

Dies dient vermutlich dazu, die Wertkategorie sowie die Konstanz des Wrappers beim Zugriff auf den umschlossenen Wert beizubehalten.

Dies macht einen Unterschied, ob const rvalue ref-qualifizierte Funktionen für das umschlossene Objekt aufgerufen werden können. Trotzdem kenne ich keine Verwendungszwecke für qualifizierte Funktionen mit konstantem Wert.

Eerorika
quelle
1

Ich kann mir keine Situation vorstellen, in der dies direkt nützlich wäre, aber es könnte indirekt verwendet werden:

template<class T>
void f(T const &x) {
  cout << "lvalue";
}
template<class T>
void f(T &&x) {
  cout << "rvalue";
}

template<class T>
void g(T &x) {
  f(T());
}

template<class T>
void h(T const &x) {
  g(x);
}

Das T in g ist T const, also ist f 's x ein T const &&.

Es ist wahrscheinlich, dass dies zu einem Comile-Fehler in f führt (wenn versucht wird, das Objekt zu verschieben oder zu verwenden), aber f könnte einen rvalue-ref verwenden, so dass es nicht für lvalues ​​aufgerufen werden kann, ohne den rvalue zu ändern (wie im zu einfachen Beispiel oben).

Fred Nurk
quelle