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
.
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.
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:
W ten sposób możemy używać fabryki w następujący sposób:]}Gdy typ parametru funkcji jest formularz
T&&
gdzieT
jest szablonem parametr i argument funkcji jest lvalue typuA
, typA&
jest używany do dedukcji argumentów szablonu.
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);
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.
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
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
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