Ответ 1
Это не ссылка ссылки, а скорее новая языковая функция, называемая ссылка rvalue, которая представляет (неофициально) ссылку на объект в памяти, который не упоминается в другом месте программы и может быть разрушенно изменен. Например, возвращаемое значение функции может быть записано с помощью ссылки rvalue, а также временные значения, введенные в выражения.
Ссылки Rvalue могут использоваться для различных целей. С точки зрения большинства программистов на C++ их можно использовать для реализации переместить семантику, посредством чего новый объект может быть инициализирован путем "перемещения" содержимого старого объекта из старого объекта и в новый объект. Вы можете использовать это, чтобы возвращать огромные объекты из функций в С++ 11, не платя огромных затрат на копирование объекта, поскольку объект, используемый для захвата возвращаемого значения, может быть инициализирован с помощью конструктора перемещения, просто украв внутренности из временного объекта созданный оператором return.
Семантика перемещения ортогональна к семантике копирования, поэтому объекты могут перемещаться без возможности копирования. Например, std::ofstream
не могут быть скопированы, но они будут подвижными, поэтому вы можете вернуть std::ofstream
из функций, используя поведение перемещения. Это невозможно сделать в С++ 03. Например, этот код является незаконным в С++ 03, но отлично (и рекомендуется!) В С++ 11:
std::ifstream GetUserFile() {
while (true) {
std::cout << "Enter filename: ";
std::string filename;
std::getline(std::cin, filename);
ifstream input(filename); // Note: No .c_str() either!
if (input) return input;
std::cout << "Sorry, I couldn't open that file." << std::endl;
}
}
std::ifstream file = GetUserFile(); // Okay, move stream out of the function.
Интуитивно, функция, которая принимает ссылку rvalue, является функцией, которая (вероятно) пытается избежать дорогостоящей копии, перемещая содержимое старого объекта в новый объект. Например, вы можете определить конструктор перемещения для векторного объекта, используя этот конструктор в ссылке rvalue. Если мы представляем вектор как тройку указателя на массив, емкость массива и используемое пространство, мы можем реализовать его конструктор перемещения следующим образом:
vector::vector(vector&& rhs) {
/* Steal resources from rhs. */
elems = rhs.elems;
size = rhs.size;
capacity = rhs.capacity;
/* Destructively modify rhs to avoid having two objects sharing
* an underlying array.
*/
rhs.elems = nullptr; // Note use of nullptr instead of NULL
rhs.size = 0;
rhs.capacity = 0;
}
Важно заметить, что когда мы очищаем rhs
в конце конструктора, мы вставляем rhs
в такое состояние, что
- Не вызывает сбоя при вызове деструктора (обратите внимание, что мы установили его указатель на
nullptr
, так как освобождениеnullptr
является безопасным) и - Тем не менее объект присваивает новое значение. Этот последний момент сложный, но важно убедиться, что вы все равно можете дать очищенному объекту новое значение в какой-то момент. Это связано с тем, что можно получить ссылку rvalue на объект, на который по-прежнему можно ссылаться позже в программе.
Чтобы пролить свет на (2), один интересный пример использования для ссылок rvalue - это способность явно перемещать значения между объектами. Например, рассмотрим эту идиоматическую реализацию swap
:
template <typename T> void swap(T& lhs, T& rhs) {
T temp = lhs;
lhs = rhs;
rhs = temp;
}
Этот код является законным, но это немного необычно. В частности, он заканчивает создание трех копий - сначала при установке temp
, равной копии lhs
, после установки lhs
в качестве копии rhs
и после установки rhs
в качестве копии temp
. Но мы действительно не хотим делать какие-либо копии вообще; вместо этого мы просто хотим перетасовать ценности вокруг. Следовательно, в С++ 11 вы сможете явно получать ссылки rvalue на объекты с помощью функции std::move
:
template <typename T> void swap(T& lhs, T& rhs) {
T temp = std::move(lhs);
lhs = std::move(rhs);
rhs = std::move(temp);
}
Теперь никаких копий вообще нет. Мы перемещаем содержимое lhs
в temp
, затем перемещаем содержимое rhs
в lhs
, а затем перемещаем содержимое temp
в rhs
. При этом мы оставляли как lhs
, так и rhs
в состоянии "опорожнения" временно, прежде чем вводить в них новые значения. Важно, чтобы при написании кода для перемещения содержимого из объекта мы оставляли объект в несколько хорошо сформированном состоянии, чтобы этот код работал правильно.
Надеюсь, это поможет!