Ссылки очень похожи на указатели, но они специально созданы для оптимизации компиляторов.
- Ссылки составлены таким образом, что компилятору существенно легче отслеживать, какие ссылочные псевдонимы какие переменные. Две важные особенности очень важны: нет «ссылочной арифметики» и нет переназначения ссылок. Это позволяет компилятору выяснить, какие ссылки ссылаются на какие переменные во время компиляции.
- Ссылки могут ссылаться на переменные, которые не имеют адресов памяти, например, те, которые компилятор выбирает для включения в регистры. Если вы берете адрес локальной переменной, компилятору будет очень трудно поместить его в регистр.
Как пример:
void maybeModify(int& x); // may modify x in some way
void hurtTheCompilersOptimizer(short size, int array[])
{
// This function is designed to do something particularly troublesome
// for optimizers. It will constantly call maybeModify on array[0] while
// adding array[1] to array[2]..array[size-1]. There's no real reason to
// do this, other than to demonstrate the power of references.
for (int i = 2; i < (int)size; i++) {
maybeModify(array[0]);
array[i] += array[1];
}
}
Оптимизирующий компилятор может понять, что мы обращаемся к [0] и [1] довольно много. Хотелось бы оптимизировать алгоритм так:
void hurtTheCompilersOptimizer(short size, int array[])
{
// Do the same thing as above, but instead of accessing array[1]
// all the time, access it once and store the result in a register,
// which is much faster to do arithmetic with.
register int a0 = a[0];
register int a1 = a[1]; // access a[1] once
for (int i = 2; i < (int)size; i++) {
maybeModify(a0); // Give maybeModify a reference to a register
array[i] += a1; // Use the saved register value over and over
}
a[0] = a0; // Store the modified a[0] back into the array
}
Для такой оптимизации необходимо доказать, что ничто не может изменить массив [1] во время вызова. Это довольно легко сделать. i никогда не меньше 2, поэтому массив [i] никогда не может ссылаться на массив [1]. MaybeModify () получает a0 в качестве ссылки (псевдоним массива [0]). Поскольку здесь нет «ссылочной» арифметики, компилятору нужно только доказать, что MaybeModify никогда не получает адрес x, и он доказал, что ничего не меняет массив [1].
Это также должно доказать, что нет никаких способов, которыми будущий вызов мог бы прочитать / записать [0], пока у нас есть временная регистровая копия этого в a0. Это часто тривиально доказать, потому что во многих случаях очевидно, что ссылка никогда не сохраняется в постоянной структуре, такой как экземпляр класса.
Теперь сделайте то же самое с указателями
void maybeModify(int* x); // May modify x in some way
void hurtTheCompilersOptimizer(short size, int array[])
{
// Same operation, only now with pointers, making the
// optimization trickier.
for (int i = 2; i < (int)size; i++) {
maybeModify(&(array[0]));
array[i] += array[1];
}
}
Поведение такое же; только теперь гораздо труднее доказать, что MaybeModify никогда не модифицирует массив [1], потому что мы уже дали ему указатель; кошка вышла из сумки. Теперь он должен сделать гораздо более сложное доказательство: статический анализ MaybeModify, чтобы доказать, что он никогда не пишет в & x + 1. Он также должен доказать, что он никогда не сохраняет указатель, который может ссылаться на массив [0], как хитрый.
Современные компиляторы становятся все лучше и лучше в статическом анализе, но всегда приятно выручать их и использовать ссылки.
Конечно, за исключением таких умных оптимизаций, компиляторы действительно превращают ссылки в указатели, когда это необходимо.
РЕДАКТИРОВАТЬ: Через пять лет после публикации этого ответа я обнаружил реальную техническую разницу, когда ссылки отличаются от просто другого взгляда на одну и ту же концепцию адресации. Ссылки могут изменить продолжительность жизни временных объектов так, как указатели не могут.
F createF(int argument);
void extending()
{
const F& ref = createF(5);
std::cout << ref.getArgument() << std::endl;
};
Обычно временные объекты, такие как объект, созданный при вызове createF(5)
, уничтожаются в конце выражения. Однако, связывая этот объект со ссылкой, ref
, C ++ продлит срок службы этого временного объекта до тех пор, пока ref
не выйдет из области видимости.