Интуитивное понимание функций, ссылающихся на ссылки

Возможный дубликат:
Что делает T && в С++ 11?

По какой-то причине это ускользает от моей интуиции, и я не могу найти никаких объяснений в Интернете. Что означает, что для функции С++ требуется ссылка на ссылку? Например:

void myFunction(int&& val);     //what does this mean?!

Я понимаю идею передачи по ссылке, поэтому

void addTwo(int& a)
{
    a += 2;
}

int main()
{
    int x = 5;
    addTwo(x);

    return 0;
}

работает и интуитивно понятен мне.

Ответы

Ответ 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 в состоянии "опорожнения" временно, прежде чем вводить в них новые значения. Важно, чтобы при написании кода для перемещения содержимого из объекта мы оставляли объект в несколько хорошо сформированном состоянии, чтобы этот код работал правильно.

Надеюсь, это поможет!

Ответ 2

Это не ссылка на ссылку. Это новый синтаксис, введенный в С++ 0x для так называемых Rvalue ссылок.