Was bedeutet T&& (doppeltes kaufmännisches Und) in C++11?

Lesezeit: 14 Minuten

Was bedeutet T doppeltes kaufmannisches Und in C11
paxdiablo

Ich habe mich mit einigen der neuen Features von C++11 beschäftigt und mir ist dabei das doppelte kaufmännische Und beim Deklarieren von Variablen aufgefallen, wie z T&& var.

Zunächst einmal, wie heißt dieses Biest? Ich wünschte, Google würde uns erlauben, nach solchen Satzzeichen zu suchen.

Was genau macht es bedeuten?

Auf den ersten Blick scheint es sich um eine Doppelreferenz zu handeln (wie die Doppelzeiger im C-Stil T** var), aber es fällt mir schwer, an einen Anwendungsfall dafür zu denken.

  • Ich habe dies in die C++-FAQ aufgenommen, da ich mir sicher bin, dass es in Zukunft öfter auftauchen wird.

    – GManNickG

    30. März 2011 um 3:50 Uhr

  • verwandte Frage zur Move-Semantik

    – fredoverflow

    30. März 2011 um 6:56 Uhr

  • Sie können mit Google danach suchen, Sie müssen Ihren Satz nur in Anführungszeichen setzen: google.com/#q=”T%26%26” hat jetzt deine Frage als ersten Treffer. :)

    – sbi

    22. Juni 2011 um 7:46 Uhr

  • Ich habe oben drei Stackoverflow-Fragen erhalten, die in Google nach “c++ two kaufmännische Und-Parameter” gesucht haben, und Ihre war die erste. Sie müssen also nicht einmal Satzzeichen verwenden, wenn Sie “zwei kaufmännische Und-Parameter” buchstabieren können.

    – sergiol

    14. August 2015 um 7:28 Uhr


  • @sergiol und @sbi: Google wird immer besser: Eine Suche nach c++ T&& type (ohne Anführungszeichen oder irgendetwas) bringt dies als ersten Treffer.

    – Peter Cordes

    4. Oktober 2016 um 22:13 Uhr

Es erklärt ein rvalue-Referenz (Standardvorschlagsdokument).

Hier ist eine Einführung in rvalue Verweise.

Hier ist ein fantastischer detaillierter Blick auf rvalue-Referenzen von einer der Standardbibliotheken von Microsoft Entwickler.

VORSICHT: der verlinkte Artikel auf MSDN (“Rvalue References: C++0x Features in VC10, Part 2”) ist eine sehr klare Einführung in Rvalue-Referenzen, macht aber Aussagen über Rvalue-Referenzen, die einst im C++11-Standardentwurf galten, gelten aber nicht für das letzte! Insbesondere heißt es an verschiedenen Stellen, dass rvalue-Referenzen an lvalues ​​binden können, was einst wahr war, aber geändert wurde (z. B. int x; int &&rrx = x; nicht mehr in GCC kompiliert)

Der größte Unterschied zwischen einer C++03-Referenz (jetzt Lvalue-Referenz in C++11 genannt) besteht darin, dass sie wie ein temporärer Wert an einen Rvalue gebunden werden kann, ohne konstant sein zu müssen. Somit ist diese Syntax jetzt legal:

T&& r = T();

rvalue-Referenzen bieten hauptsächlich Folgendes:

Semantik bewegen. Es können jetzt ein Bewegungskonstruktor und ein Bewegungszuweisungsoperator definiert werden, die eine rvalue-Referenz anstelle der üblichen const-lvalue-Referenz annehmen. Eine Verschiebung funktioniert wie eine Kopie, außer dass sie nicht verpflichtet ist, die Quelle unverändert zu lassen; Tatsächlich ändert es normalerweise die Quelle so, dass es die verschobenen Ressourcen nicht mehr besitzt. Dies ist großartig, um überflüssige Kopien zu eliminieren, insbesondere in Implementierungen von Standardbibliotheken.

Ein Kopierkonstruktor könnte beispielsweise so aussehen:

foo(foo const& other)
{
    this->length = other.length;
    this->ptr = new int[other.length];
    copy(other.ptr, other.ptr + other.length, this->ptr);
}

Wenn diesem Konstruktor ein temporäres übergeben würde, wäre die Kopie unnötig, da wir wissen, dass das temporäre nur zerstört wird; Warum nicht die Ressourcen nutzen, die das Temporär bereits zugewiesen hat? In C++03 gibt es keine Möglichkeit, das Kopieren zu verhindern, da wir nicht feststellen können, ob uns ein temporäres übergeben wurde. In C++11 können wir einen Move-Konstruktor überladen:

foo(foo&& other)
{
   this->length = other.length;
   this->ptr = other.ptr;
   other.length = 0;
   other.ptr = nullptr;
}

Beachten Sie hier den großen Unterschied: Der Move-Konstruktor ändert tatsächlich sein Argument. Dies würde das Provisorium effektiv in das zu konstruierende Objekt “bewegen”, wodurch die unnötige Kopie eliminiert würde.

Der Move-Konstruktor würde für temporäre Dateien und für nicht konstante lvalue-Referenzen verwendet, die mithilfe von explizit in rvalue-Referenzen konvertiert werden std::move Funktion (es führt nur die Konvertierung durch). Der folgende Code ruft beide den Move-Konstruktor für auf f1 und f2:

foo f1((foo())); // Move a temporary into f1; temporary becomes "empty"
foo f2 = std::move(f1); // Move f1 into f2; f1 is now "empty"

Perfekte Weiterleitung. rvalue-Referenzen ermöglichen es uns, Argumente für Vorlagenfunktionen ordnungsgemäß weiterzuleiten. Nehmen Sie zum Beispiel diese Fabrikfunktion:

template <typename T, typename A1>
std::unique_ptr<T> factory(A1& a1)
{
    return std::unique_ptr<T>(new T(a1));
}

Wenn wir anriefen factory<foo>(5)wird das Argument abgeleitet werden int&die nicht an ein Literal 5 gebunden wird, selbst wenn fooDer Konstruktor von nimmt eine int. Nun, wir könnten stattdessen verwenden A1 const&Aber was wenn foo Nimmt das Konstruktor-Argument als Nicht-Konstanten-Referenz? Um eine wirklich generische Factory-Funktion zu erstellen, müssten wir factory on überladen A1& und weiter A1 const&. Das mag in Ordnung sein, wenn Factory 1 Parametertyp verwendet, aber jeder zusätzliche Parametertyp würde das erforderliche Overload-Set mit 2 multiplizieren. Das ist sehr schnell nicht mehr zu warten.

rvalue-Referenzen beheben dieses Problem, indem sie der Standardbibliothek erlauben, a zu definieren std::forward Funktion, die lvalue/rvalue-Referenzen ordnungsgemäß weiterleiten kann. Weitere Informationen darüber, wie std::forward funktioniert, siehe diese ausgezeichnete Antwort.

Dies ermöglicht es uns, die Factory-Funktion wie folgt zu definieren:

template <typename T, typename A1>
std::unique_ptr<T> factory(A1&& a1)
{
    return std::unique_ptr<T>(new T(std::forward<A1>(a1)));
}

Jetzt wird die rvalue/lvalue-ness des Arguments beibehalten, wenn es übergeben wird TDer Konstruktor von . Das heißt, wenn Factory mit einem Rvalue aufgerufen wird, TDer Konstruktor von wird mit einem Rvalue aufgerufen. Wenn factory mit einem lvalue aufgerufen wird, TDer Konstruktor von wird mit einem Lvalue aufgerufen. Die verbesserte Fabrikfunktion funktioniert aufgrund einer besonderen Regel:

Wenn der Funktionsparametertyp das Format hat T&& wo T ist ein Vorlagenparameter, und das Funktionsargument ist ein lvalue vom Typ Ader Typ A& wird für die Ableitung von Vorlagenargumenten verwendet.

Somit können wir die Fabrik wie folgt verwenden:

auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1);   // calls foo(foo const&)

Wichtige rvalue-Referenzeigenschaften:

  • Für die Überlastauflösung lvalues ​​bevorzugen die Bindung an lvalue-Referenzen und rvalues ​​bevorzugen die Bindung an rvalue-Referenzen. Daher ziehen es Temporäre vor, einen Verschiebekonstruktor/Verschiebezuweisungsoperator gegenüber einem Kopierkonstruktor/Zuweisungsoperator aufzurufen.
  • Rvalue-Referenzen werden implizit an Rvalues ​​und temporäre Dateien gebunden, die das Ergebnis einer impliziten Konvertierung sind. dh float f = 0f; int&& i = f; ist wohlgeformt, weil float implizit in int konvertierbar ist; der Verweis wäre auf ein temporäres Objekt, das das Ergebnis der Konvertierung ist.
  • Benannte Rvalue-Referenzen sind Lvalues. Unbenannte Rvalue-Referenzen sind Rvalues. Dies ist wichtig zu verstehen, warum die std::move Anruf ist erforderlich in: foo&& r = foo(); foo f = std::move(r);

  • +1 für Named rvalue references are lvalues. Unnamed rvalue references are rvalues.; ohne das zu wissen, habe ich Mühe zu verstehen, warum Menschen a tun T &&t; std::move

    – legends2k

    30. Mai 2013 um 12:32 Uhr


  • @MaximYegorushkin: In diesem Beispiel ist r an einen reinen rvalue (temporär) gebunden und daher sollte der Gültigkeitsbereich des temporären Lebens verlängert werden, oder?

    – Peter Hüne

    31. Juli 2013 um 19:26 Uhr

  • @PeterHuene Ich nehme das zurück, eine R-Wert-Referenz verlängert die Lebensdauer eines Temporärs.

    – Maxim Egorushkin

    31. Juli 2013 um 21:30 Uhr

  • VORSICHT: der verlinkte Artikel auf MSDN ("Rvalue References: C++0x Features in VC10, Part 2") ist eine sehr klare Einführung in Rvalue-Referenzen, aber macht Aussagen über Rvalue-Referenzen, die waren Einmal im Entwurf des C++11-Standards wahr, sind es aber nicht wahr für das letzte! Insbesondere heißt es an verschiedenen Stellen, dass rvalue-Referenzen an lvalues ​​binden können, was einmal wahr war, aber wurde verändert.(z.B int x; int &&rrx = x; kompiliert nicht mehr im GCC)

    – gezogene Widerhaken

    13. Juli 2014 um 16:12 Uhr


  • Könnte jemand zum besseren Verständnis erklären, ob die folgenden Aussagen falsch sind 1. rvalues können als Provisorien betrachtet werden, deren Lebensdauer nicht garantiert ist. 2. foo&& r = foo() verlängert die Lebensdauer der Rückkehr von foo() für die Dauer des Geltungsbereichs. 3. Sind diese gleichwertig: foo&& r und const foo& r?

    – Olschansk

    26. Oktober 2020 um 19:27 Uhr

Was bedeutet T doppeltes kaufmannisches Und in C11
Hündchen

Es bezeichnet eine Rvalue-Referenz. Rvalue-Referenzen werden nur an temporäre Objekte gebunden, sofern nicht ausdrücklich anders generiert. Sie werden verwendet, um Objekte unter bestimmten Umständen viel effizienter zu machen und um eine Funktion bereitzustellen, die als perfekte Weiterleitung bekannt ist und den Vorlagencode erheblich vereinfacht.

In C++03 können Sie nicht zwischen einer Kopie eines nicht veränderlichen lvalue und einem rvalue unterscheiden.

std::string s;
std::string another(s);           // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(const std::string&);

In C++0x ist dies nicht der Fall.

std::string s;
std::string another(s);           // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(std::string&&);

Betrachten Sie die Implementierung hinter diesen Konstruktoren. Im ersten Fall muss die Zeichenfolge eine Kopie ausführen, um die Wertsemantik beizubehalten, was eine neue Heap-Zuordnung beinhaltet. Im zweiten Fall wissen wir jedoch im Voraus, dass das an unseren Konstruktor übergebene Objekt sofort zur Vernichtung fällig ist und nicht unberührt bleiben muss. Wir können in diesem Szenario effektiv nur die internen Zeiger austauschen und überhaupt kein Kopieren durchführen, was wesentlich effizienter ist. Move-Semantik kommt jeder Klasse zugute, die teures oder verbotenes Kopieren von intern referenzierten Ressourcen hat. Betrachten Sie den Fall von std::unique_ptr- Jetzt, da unsere Klasse zwischen Temporären und Nicht-Provisorischen unterscheiden kann, können wir dafür sorgen, dass die Move-Semantik korrekt funktioniert, sodass die unique_ptr können nicht kopiert, aber verschoben werden, was bedeutet, dass std::unique_ptr können legal in Standardcontainern gespeichert, sortiert usw. werden, während C++03's std::auto_ptr kann nicht.

Jetzt betrachten wir die andere Verwendung von rvalue-Referenzen – perfekte Weiterleitung. Betrachten Sie die Frage der Bindung einer Referenz an eine Referenz.

std::string s;
std::string& ref = s;
(std::string&)& anotherref = ref; // usually expressed via template

Ich kann mich nicht erinnern, was C++03 dazu sagt, aber in C++0x ist der resultierende Typ beim Umgang mit rvalue-Referenzen entscheidend. Eine rvalue-Referenz auf einen Typ T, wobei T ein Referenztyp ist, wird zu einer Referenz vom Typ T.

(std::string&)&& ref // ref is std::string&
(const std::string&)&& ref // ref is const std::string&
(std::string&&)&& ref // ref is std::string&&
(const std::string&&)&& ref // ref is const std::string&&

Betrachten Sie die einfachste Vorlagenfunktion - min und max. In C++03 müssen Sie für alle vier Kombinationen von Konstante und Nicht-Konstante manuell überladen. In C++0x ist es nur eine Überladung. Kombiniert mit diversen Templates ermöglicht dies eine perfekte Weiterleitung.

template<typename A, typename B> auto min(A&& aref, B&& bref) {
    // for example, if you pass a const std::string& as first argument,
    // then A becomes const std::string& and by extension, aref becomes
    // const std::string&, completely maintaining it's type information.
    if (std::forward<A>(aref) < std::forward<B>(bref))
        return std::forward<A>(aref);
    else
        return std::forward<B>(bref);
}

Ich habe den Rückgabetypabzug weggelassen, weil ich mich nicht erinnern kann, wie es auf Anhieb gemacht wurde, aber dieses min kann jede Kombination von lvalues, rvalues, const lvalues ​​akzeptieren.

  • warum du benutzt hast std::forward<A>(aref) < std::forward<B>(bref)? und ich glaube nicht, dass diese Definition korrekt sein wird, wenn Sie es versuchen int& und float&. Löschen Sie besser eine Art Formularvorlage.

    – Amis

    4. Juli 2013 um 13:24 Uhr


1647120618 440 Was bedeutet T doppeltes kaufmannisches Und in C11
mmocny

Der Begriff für T&& bei Verwendung mit Typabzug (z. B. für Perfect Forwarding) wird umgangssprachlich als a bezeichnet Weiterleitung Referenz. Der Begriff „Universal Reference“ wurde von Scott Meyers geprägt In diesem Artikelwurde aber später geändert.

Das liegt daran, dass es entweder ein R-Wert oder ein L-Wert sein kann.

Beispiele sind:

// template
template<class T> foo(T&& t) { ... }

// auto
auto&& t = ...;

// typedef
typedef ... T;
T&& t = ...;

// decltype
decltype(...)&& t = ...;

Weitere Diskussionen finden Sie in der Antwort für: Syntax für universelle Referenzen

1647120619 475 Was bedeutet T doppeltes kaufmannisches Und in C11
Kurt Krückeberg

Eine rvalue-Referenz ist ein Typ, der sich mit einigen Ausnahmen ähnlich wie die gewöhnliche Referenz X& verhält. Der wichtigste ist, dass lvalues ​​bei der Auflösung von Funktionsüberladungen lvalue-Referenzen im alten Stil bevorzugen, während rvalues ​​die neuen rvalue-Referenzen bevorzugen:

void foo(X& x);  // lvalue reference overload
void foo(X&& x); // rvalue reference overload

X x;
X foobar();

foo(x);        // argument is lvalue: calls foo(X&)
foo(foobar()); // argument is rvalue: calls foo(X&&)

Was ist also ein rvalue? Alles, was kein lvalue ist. Ein lvalue ist ein Ausdruck, der sich auf einen Speicherort bezieht und es uns ermöglicht, die Adresse dieses Speicherorts über den &-Operator zu übernehmen.

Es ist fast einfacher zu verstehen, was rvalues ​​zuerst mit einem Beispiel bewirken:

 #include <cstring>
 class Sample {
  int *ptr; // large block of memory
  int size;
 public:
  Sample(int sz=0) : ptr{sz != 0 ? new int[sz] : nullptr}, size{sz} 
  {
     if (ptr != nullptr) memset(ptr, 0, sz);
  }
  // copy constructor that takes lvalue 
  Sample(const Sample& s) : ptr{s.size != 0 ? new int[s.size] :\
      nullptr}, size{s.size}
  {
     if (ptr != nullptr) memcpy(ptr, s.ptr, s.size);
     std::cout << "copy constructor called on lvalue\n";
  }

  // move constructor that take rvalue
  Sample(Sample&& s) 
  {  // steal s's resources
     ptr = s.ptr;
     size = s.size;        
     s.ptr = nullptr; // destructive write
     s.size = 0;
     cout << "Move constructor called on rvalue." << std::endl;
  }    
  // normal copy assignment operator taking lvalue
  Sample& operator=(const Sample& s)
  {
   if(this != &s) {
      delete [] ptr; // free current pointer
      size = s.size;

      if (size != 0) {
        ptr = new int[s.size];
        memcpy(ptr, s.ptr, s.size);
      } else 
         ptr = nullptr;
     }
     cout << "Copy Assignment called on lvalue." << std::endl;
     return *this;
  }    
 // overloaded move assignment operator taking rvalue
 Sample& operator=(Sample&& lhs)
 {
   if(this != &s) {
      delete [] ptr; //don't let ptr be orphaned 
      ptr = lhs.ptr;   //but now "steal" lhs, don't clone it.
      size = lhs.size; 
      lhs.ptr = nullptr; // lhs's new "stolen" state
      lhs.size = 0;
   }
   cout << "Move Assignment called on rvalue" << std::endl;
   return *this;
 }
//...snip
};     

Die Konstruktor- und Zuweisungsoperatoren wurden mit Versionen überladen, die Rvalue-Referenzen annehmen. Rvalue-Referenzen ermöglichen es einer Funktion, zur Kompilierzeit (über die Überladungsauflösung) unter der Bedingung „Werde ich von einem Lvalue oder einem Rvalue aufgerufen?“ zu verzweigen. Dadurch konnten wir effizientere Konstruktoren und Zuweisungsoperatoren oben erstellen, die Ressourcen verschieben, anstatt sie zu kopieren.

Der Compiler verzweigt sich automatisch zur Kompilierzeit (abhängig davon, ob er für einen lvalue oder einen rvalue aufgerufen wird) und wählt aus, ob der Move-Konstruktor oder der Move-Zuweisungsoperator aufgerufen werden soll.

Zusammenfassend: rvalue-Referenzen ermöglichen Bewegungssemantik (und perfekte Weiterleitung, die im Artikellink unten besprochen wird).

Ein praktisches, leicht verständliches Beispiel ist die Klassenvorlage std::unique_ptr. Da ein unique_ptr das exklusive Eigentum an seinem zugrunde liegenden Rohzeiger behält, können die von unique_ptr nicht kopiert werden. Das würde ihre Invariante des ausschließlichen Eigentums verletzen. Sie haben also keine Kopierkonstruktoren. Aber sie haben Bewegungskonstruktoren:

template<class T> class unique_ptr {
  //...snip
 unique_ptr(unique_ptr&& __u) noexcept; // move constructor
};

 std::unique_ptr<int[] pt1{new int[10]};  
 std::unique_ptr<int[]> ptr2{ptr1};// compile error: no copy ctor.  

 // So we must first cast ptr1 to an rvalue 
 std::unique_ptr<int[]> ptr2{std::move(ptr1)};  

std::unique_ptr<int[]> TakeOwnershipAndAlter(std::unique_ptr<int[]> param,\
 int size)      
{
  for (auto i = 0; i < size; ++i) {
     param[i] += 10;
  }
  return param; // implicitly calls unique_ptr(unique_ptr&&)
}

// Now use function     
unique_ptr<int[]> ptr{new int[10]};

// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(\
           static_cast<unique_ptr<int[]>&&>(ptr), 10);

cout << "output:\n";

for(auto i = 0; i< 10; ++i) {
   cout << new_owner[i] << ", ";
}

output:
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 

static_cast<unique_ptr<int[]>&&>(ptr) erfolgt in der Regel mit std::bewegen

// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(std::move(ptr),0);

Ein ausgezeichneter Artikel, der all dies und mehr erklärt (z. B. wie R-Werte eine perfekte Weiterleitung ermöglichen und was das bedeutet) mit vielen guten Beispielen ist der von Thomas Becker C++ Rvalue-Referenzen erklärt. Dieser Beitrag stützte sich stark auf seinen Artikel.

Eine kürzere Einführung ist Eine kurze Einführung in Rvalue-Referenzen von Stroutrup, et. Al

995230cookie-checkWas bedeutet T&& (doppeltes kaufmännisches Und) in C++11?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy