Co oznacza T& & (double ampersand) w C++11?

Przyglądałem się kilku nowym funkcjom C++11 i jedną z nich zauważyłem jest podwójne ampersand w deklarowaniu zmiennych, takich jak T&& var.

Na początek, jak nazywa się ta bestia? Szkoda, że Google nie pozwala nam wyszukiwać takich interpunkcji.

Co dokładnie oznacza ?

Na pierwszy rzut oka wydaje się być podwójnym odniesieniem (jak podwójne wskaźniki w stylu C T** var), ale trudno mi myśleć o przypadku użycia.

Author: paxdiablo, 2011-03-30

4 answers

Deklaruje rvalue reference (standards proposal doc).

Oto wprowadzenie do rvalue references.

[[30]}oto fantastyczne dogłębne spojrzenie na referencje rvalue przez jednego z programistów standardowej biblioteki Microsoftu . (Ale zobacz ostrożność w komentarzach po tej odpowiedzi przed przeczytaniem tego artykułu.)

Największą różnicą między odniesieniem C++03 (obecnie nazywanym odniesieniem lvalue w C++11) jest to, że może wiązać się z rvalue jak tymczasowy bez konieczności const. Tak więc składnia ta jest teraz legalna: {]}

T&& r = T();

Odniesienia Rvalue przewidują przede wszystkim:

Semantyka ruchu . Konstruktor move i operator przypisania move mogą być teraz zdefiniowane, które przyjmują odniesienie rvalue zamiast zwykłego odniesienia const-lvalue. Ruch działa jak kopia, z tym, że nie jest zobowiązany do zachowania niezmienionego źródła; w rzeczywistości zwykle modyfikuje źródło tak, że nie jest już właścicielem przesunięte zasoby. Jest to idealne rozwiązanie do eliminowania obcych kopii, szczególnie w standardowych implementacjach bibliotek.

Na przykład Konstruktor kopiujący może wyglądać tak:

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

Jeśli ten konstruktor został przekazany jako tymczasowy, Kopia byłaby niepotrzebna, ponieważ wiemy, że tymczasowy zostanie po prostu zniszczony; dlaczego nie skorzystać z zasobów, które tymczasowo już przydzielono? W C++03 nie ma sposobu, aby zapobiec kopiowaniu, ponieważ nie możemy określić, że zostaliśmy przekazani jako tymczasowi. W C++11 możemy przeciążać konstruktor ruchu:

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

Zauważ dużą różnicę: konstruktor move faktycznie modyfikuje swój argument. To skutecznie "przeniesie" tymczasowy obiekt do budowanego obiektu, eliminując w ten sposób niepotrzebną kopię.

Konstruktor move będzie używany dla odniesień tymczasowych i niekonstrukcyjnych, które są jawnie konwertowane do odniesień rvalue za pomocą funkcji std::move (po prostu wykonuje konwersję). Następujące kod wywołujący konstruktor ruchu dla f1 i 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"

Doskonałe przekierowanie . referencje rvalue pozwalają nam na prawidłowe przekazywanie argumentów dla funkcji szablonowych. Weźmy na przykład tę funkcję fabryczną:

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

Jeśli wywołamy factory<foo>(5), argument zostanie wydedukowany jako int&, który nie będzie wiązał się z literalnym 5, nawet jeśli konstruktor foo przyjmie int. Cóż, możemy zamiast tego użyć A1 const&, Ale co jeśli foo weźmie argument konstruktora przez non-const reference? Aby stworzyć prawdziwie generyczną funkcję fabryczną, musielibyśmy przeciążyć factory na A1& i na A1 const&. To może być w porządku, jeśli fabryka przyjmuje 1 typ parametru, ale każdy dodatkowy typ parametru mnoży niezbędny zestaw przeciążeń przez 2. To bardzo szybko niemożliwe do utrzymania.

Rvalue references rozwiązuje ten problem, pozwalając bibliotece standardowej zdefiniować funkcję std::forward, która może poprawnie przekazywać odwołania lvalue/rvalue. Aby uzyskać więcej informacji o tym, jak std::forward działa, zobacz ta doskonała odpowiedź .

To pozwala nam zdefiniować funkcję fabryczną w następujący sposób:

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

Teraz argument rvalue / lvalue - ness jest zachowywany po przekazaniu do konstruktora T. Oznacza to, że jeśli fabryka jest wywoływana z wartością r, konstruktor T jest wywoływany z wartością r. Jeśli fabryka jest wywoływana z lvalue, konstruktor T jest wywoływany z lvalue. Ulepszona funkcja fabryczna działa dzięki jednej specjalnej zasadzie:

Gdy typ parametru funkcji jest formularz T&& gdzie T jest szablonem parametr i argument funkcji jest lvalue typu A, typ A& jest używany do dedukcji argumentów szablonu.

W ten sposób możemy używać fabryki w następujący sposób:]}
auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1);   // calls foo(foo const&)

Ważne właściwości referencyjne rvalue :

  • dla rozdzielczości przeciążenia, lvalues preferują powiązanie z referencjami lvalue, a rvalues preferują powiązanie z referencjami rvalue . Stąd też czasowniki preferują wywoływanie konstruktora / operatora przyporządkowania ruchu zamiast konstruktora kopiującego / operatora przyporządkowania.
  • odniesienia rvalue będą pośrednio wiązały się z wartościami R i tymczasowymi, które są wynikiem konwersji implicit . tzn. {[27] } jest dobrze uformowane, ponieważ float jest w domyśle zamieniany na int; odniesienie do tymczasowej, która jest wynikiem konwersji.
  • nazwane odniesienia rvalue to lvalues. Rvalue bez nazwy odniesienia są wartościami r. jest to ważne, aby zrozumieć, dlaczego wywołanie {[7] } jest konieczne w: foo&& r = foo(); foo f = std::move(r);
 558
Author: Peter Huene,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-31 12:56:38

Oznacza referencję rvalue. Odniesienia Rvalue będą wiązać się tylko z obiektami tymczasowymi, chyba że jawnie wygenerowano inaczej. Są one używane, aby obiekty były znacznie bardziej wydajne w pewnych okolicznościach i aby zapewnić obiekt znany jako doskonałe przekazywanie, co znacznie upraszcza kod szablonu.

W C++03 nie można odróżnić kopii nie-mutowalnego lvalue od rvalue.

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&);

W C++0x tak nie jest.

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

Rozważmy realizacja tych konstruktorów. W pierwszym przypadku łańcuch znaków musi wykonać kopię, aby zachować semantykę wartości, co wiąże się z nową alokacją sterty. Jednak w drugim przypadku wiemy z góry, że obiekt, który został przekazany naszemu konstruktorowi, jest natychmiast gotowy do zniszczenia i nie musi pozostać nietknięty. Możemy skutecznie po prostu zamienić wewnętrzne wskaźniki i nie wykonywać żadnego kopiowania w tym scenariuszu, co jest znacznie bardziej efektywne. Semantyka ruchu korzystaj z każdej klasy, która ma kosztowne lub zabronione kopiowanie zasobów odwołanych wewnętrznie. Rozważmy przypadek std::unique_ptr - Teraz, gdy nasza klasa może rozróżniać tymczasowe i nie-tymczasowe, możemy sprawić, że semantyka move będzie działać poprawnie tak, że unique_ptr nie może być kopiowana, ale może być przenoszona, co oznacza, że std::unique_ptr może być legalnie przechowywana w standardowych kontenerach, sortowana itp., Podczas Gdy std::auto_ptr C++03 nie może.

Teraz rozważamy inne użycie referencji rvalue-perfect forwarding. Rozważmy kwestię wiązania odniesienia do odniesienia.

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

Nie pamiętam, co mówi o tym C++03, ale w C++0x, wynikowy typ w przypadku referencji rvalue jest krytyczny. Rvalue reference to a type T, where t is a reference type, becomes a reference of type 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&&

Rozważmy najprostszą funkcję szablonu-min i max. W C++03 trzeba przeciążać wszystkie cztery kombinacje const i non-const ręcznie. W C++0x to tylko jedno przeciążenie. W połączeniu z różnymi szablonami umożliwia to doskonałe przekazywanie.

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

Opuściłem dedukcję typu zwrotu, ponieważ nie pamiętam, jak to się robi od ręki, ale ten min może zaakceptować dowolną kombinację lvalues, rvalues, const lvalues.

 76
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-06 17:35:51

Określenie na T&& w przypadku użycia z dedukcją typu (np. dla doskonałego przekierowania) jest potocznie znany jako odniesienie do przekierowania . Termin "universal reference" został wymyślony przez Scotta Meyersa w tym artykule, ale został później zmieniony.

To dlatego, że może być albo r-wartość lub L-wartość.

Przykłady to:

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

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

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

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

Więcej dyskusji można znaleźć w odpowiedzi na: składnia dla odniesień uniwersalnych

 18
Author: mmocny,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-10 15:04:23

Referencja rvalue jest typem, który zachowuje się podobnie do zwykłego referencji X&, z kilkoma wyjątkami. Najważniejsze jest to, że jeśli chodzi o rozdzielczość przeciążenia funkcji, lvalue preferuje odniesienia w starym stylu lvalue, podczas gdy rvalue preferuje nowe odniesienia rvalue:

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&&)

Więc co to jest rvalue? Wszystko, co nie jest lvalue. Istota lvalue wyrażenie, które odnosi się do lokalizacji pamięci i pozwala nam pobrać adres tej lokalizacji pamięci za pomocą operatora&.

Prawie łatwiej jest najpierw zrozumieć, co wartości R osiągają na przykładzie:

 class Sample {
  int *ptr; // large block of memory
  int size;
 public:
  Sample(int sz=0) : ptr{sz != 0 ? new int[sz] : nullptr}, size{sz} 
  {}
  // copy constructor that takes lvalue 
  Sample(const Sample& s) : ptr{s.size != 0 ? new int[s.size] :\
      nullptr}, size{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
      ptr = new int[s.size]; 
      size = s.size; 
    }
    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
};     

Konstruktor i operatory przypisań zostały przeciążone wersjami, które przyjmują odwołania do rvalue. Referencje Rvalue pozwalają funkcji rozgałęziać się w czasie kompilacji (poprzez rozdzielczość przeciążenia) pod warunkiem "Am I being called on an lvalue or an rvalue?". pozwoliło nam to stworzyć bardziej wydajny konstruktor i operatory przypisań powyżej, które przenoszą zasoby, a raczej kopiują oni.

Kompilator automatycznie rozgałęzia się w czasie kompilacji (w zależności od tego, czy jest wywoływany dla lvalue czy rvalue) wybierając, czy konstruktor move lub operator przypisania move mają być wywoływane.

Podsumowując: referencje rvalue umożliwiają semantykę przenoszenia (oraz doskonałe przekazywanie, omówione w artykule link poniżej).

Praktycznym, łatwym do zrozumienia przykładem jest szablon klasy std:: unique_ptr. Ponieważ unique_ptr utrzymuje wyłączna własność jego podstawowego surowego wskaźnika, unique_ptr nie może być kopiowana. To naruszyłoby ich niezmienność wyłącznej własności. Nie mają więc konstruktorów kopiujących. Ale mają konstruktory ruchu:

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) zwykle odbywa się za pomocą std:: move

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

Doskonały artykuł wyjaśniający to wszystko i więcej (jak np. jak wartości R umożliwiają doskonałe przekazywanie i co to oznacza) z wieloma dobrymi przykładami jest Thomas Becker C++ rvalue reference Wyjaśnione . Ten post opierał się w dużej mierze na jego artykule.

Krótszym wstępem jest Krótkie wprowadzenie do referencji Rvalue autorstwa Stroutrup, et. al

 8
Author: kurt krueckeberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-12 17:02:08