Chyba nie, ale chciałbym potwierdzić. Czy jest pożytek const Foo&&
, gdzie Foo
jest typ klasy?
Chyba nie, ale chciałbym potwierdzić. Czy jest pożytek const Foo&&
, gdzie Foo
jest typ klasy?
Odpowiedzi:
Czasami są przydatne. Sam szkic C ++ 0x używa ich w kilku miejscach, na przykład:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
Powyższe dwa przeciążeniu zapewnia, że druga ref(T&)
i cref(const T&)
funkcje nie wiążą się rvalues (które w innym przypadku byłyby możliwe).
Aktualizacja
Właśnie sprawdziłem oficjalny standard N3290 , który niestety nie jest publicznie dostępny i ma w 20.8 Function objects [function.objects] / p2:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
Następnie sprawdziłem najnowszą wersję roboczą po C ++ 11, która jest publicznie dostępna, N3485 , aw 20.8 Function objects [function.objects] / p2 nadal mówi:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
const T&&
są używane?
const T&&
zapobiega głupiemu używaniu jawnych argumentów szablonu formularza ref<const A&>(...)
. To nie jest strasznie mocny argument, ale koszt const T&&
przekroczenia T&&
jest dość minimalny.
Semantyka uzyskiwania pliku odniesienia do wartości stałej r (a nie dla=delete
) polega na stwierdzeniu:
Następujący przypadek użycia mógł być IMHO dobrym przypadkiem użycia odniesienia rvalue do const , chociaż język zdecydował się nie stosować tego podejścia (patrz oryginalny post SO ).
Zwykle byłoby wskazane użycie make_unique
i make_shared
, ale oba unique_ptr
i shared_ptr
mogą być skonstruowane z surowego wskaźnika. Oba konstruktory pobierają wskaźnik według wartości i kopiują go. Oba pozwalają (tj. W sensie: nie uniemożliwiają ) kontynuacji używania oryginalnego wskaźnika przekazanego im w konstruktorze.
Poniższy kod kompiluje się i daje podwójne wolne :
int* ptr = new int(9);
std::unique_ptr<int> p { ptr };
// we forgot that ptr is already being managed
delete ptr;
Oba unique_ptr
i shared_ptr
mogłyby zapobiec powyższemu, gdyby ich odpowiednie konstruktory oczekiwały, że otrzymają surowy wskaźnik jako stałą rwartość , np. Dla unique_ptr
:
unique_ptr(T* const&& p) : ptr{p} {}
W takim przypadku podwójny wolny kod powyżej nie skompilowałby się, ale następujący:
std::unique_ptr<int> p1 { std::move(ptr) }; // more verbose: user moves ownership
std::unique_ptr<int> p2 { new int(7) }; // ok, rvalue
Zwróć na to uwagę ptr
można go nadal używać po przeniesieniu, więc potencjalny błąd nie zniknął całkowicie. Ale jeśli użytkownik jest zobowiązany do wywołania std::move
takiego błędu, podlegałoby powszechnej zasadzie: nie używaj zasobu, który został przeniesiony.
Można zapytać: OK, ale dlaczego T*
const&& p
?
Powód jest prosty, aby umożliwić tworzenie unique_ptr
ze wskaźnika stałej . Pamiętaj, że odwołanie do wartości stałej r jest bardziej ogólne niż samo odniesienie do wartości, ponieważ akceptuje zarówno const
i non-const
. Możemy więc zezwolić na:
int* const ptr = new int(9);
auto p = std::unique_ptr<int> { std::move(ptr) };
to nie poszłoby, gdybyśmy oczekiwali tylko odniesienia do wartości r (błąd kompilacji: nie można powiązać wartości stałej r z wartością r ).
W każdym razie jest już za późno, aby coś takiego zaproponować. Ale ten pomysł przedstawia rozsądne użycie odniesienia wartości r do const .
Są dozwolone, a nawet funkcje są uszeregowane w oparciu o const
, ale ponieważ nie można przejść z obiektu const, do którego się odwołuje const Foo&&
, nie są one przydatne.
const T&, T&, const T&&, T&&
Oprócz std :: ref , biblioteka standardowa również używa odwołania const rvalue w std :: as_const w tym samym celu.
template <class T>
void as_const(const T&&) = delete;
Jest również używany jako wartość zwracana w std :: optional podczas pobierania opakowanej wartości:
constexpr const T&& operator*() const&&;
constexpr const T&& value() const &&;
Jak również w 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;
Jest to przypuszczalnie w celu zachowania kategorii wartości, a także stałości opakowania podczas uzyskiwania dostępu do opakowanej wartości.
To robi różnicę, czy funkcja const rvalue ref-qualified można wywołać na opakowanym obiekcie. To powiedziawszy, nie znam żadnych zastosowań funkcji kwalifikowanych const rvalue ref.
Nie przychodzi mi do głowy sytuacja, w której byłoby to przydatne bezpośrednio, ale można by to wykorzystać pośrednio:
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);
}
T w g jest T const, więc f 's x jest T const &&.
Prawdopodobnie skutkuje to błędem kompilacji w f (gdy próbuje przenieść lub użyć obiektu), ale f może przyjąć rvalue-ref, tak że nie można go wywołać na lvalues, bez modyfikowania rvalue (jak w zbyt prostym przykład powyżej).
const&&
są bardzo ważne, chociaż nie mówi dlaczego: youtube.com/watch?v=JhgWFYfdIho#t=54m20s