Skip to the content.

Ссылки, lvalue и rvalue, время жизни переменной


У указателей есть ряд недостатков: загрязнение кода операторами * и &, указатели на указатели которые усложняют чтение кода, необходимость в постоянной проверке на равенство указателя нулю. Для того что бы исправить некоторые недостатки указателей, в С++ введены ссылки - “красивая обёртка” над указателями:

// Стоит понимать, что в ссылки зашиты указатели, 
// однако код стал значительно чище
void swap (int &a, int &b)
{
    int t = b;
    a = b;
    b = t
}

Различия ссылок и указателей (какие проблемы указателей устранены):

Ссылка не может быть неинициализированной:

int * p; // норм
int & l; // ошибка

У ссылки нет нулевого значения (необходимость в проверке на равенство нулю отпадает):

int * p = 0; // норм
int & l = 0; // ошибка

Ссылку нельзя переинициализировать:

int a = 10, b = 20;

int * p = &a; // p указывает на а
p = &b;       // p теперь указывает на b

int & l = a;  // l ссылается на а
l = b;        // а присваивается значение b

Нельзя получить адрес на ссылку и ссылку на ссылку:

int a = 10;

int *p = &a;   // p указывает на а
int **pp = &p; // pp указывает на р
int & l = a;   // l ссылается на а
int * pl = &l; // pl указывает на а
int && ll = l; // ошибка

Нельзя создавать массив ссылок (поскольку ссылки должны быть обязательно проинициализированы, а массив не сильно этому способствует. Так же стандарт не регламентирует реализацию ссылок и компилятор в праве заменить ссылки синонимами (т.е. переменными)):

int * mp[10] = {}; // массив указателей
int & ml[10] = {}; // ошибка

У ссылок отсутствует арифметика

lvalue и rvalue

Выражения в С++ можно разделить на два типа:

Указатели и ссылки могут указывать только на lvalue:

int a = 10, b = 20;
int m[10] = {1, 2, 3, 4, 5, 5, 4, 3, 2, 1};

int & l1 = a;
int & l2 = a + b; // ошибка, ведь a+b - rvalue (10+20 не поставить слева от =)
int & l3 = *(m + a / 2); // эквивалентно m[a/2], все ок
int & l4 = *(m + a / 2) + 1; // ошибка: 5 + 1
int & l5 = (a + b > 10) ? a : b; // все ок

Время жизни переменной

Может так случится что указатель/ссылка будет указывать/ссылаться на переменную которой уже не существует, следует следить за временем переменных:

int * foo()
{
    int a = 10; // локальная переменная, при выходе из foo() пропадёт
    return &a;  // однако мы возвращаем указатель на локальную а
}

int & bar()
{
    int b = 20; // локальная b
    return b;   // возвращаем ссылку на локальную b
}

int * p = foo(); // не корректно, dangling pointer
int & l = bar(); // dangling reference

Плюшки:

Массив в функцию можно передать при помощи ссылки:

void foo(int (&a)[3]) {}
// функция принимает ссылку на массив из трех элементов
// казалось бы смысла от такой записи мало: массивы и так не копируются при передаче в функцию, но она позволяет нам запретить компилятору игнорировать размер передаваемого массива
// такая странная форма записи потому что мы хотим передать именно ссылку на массив из 3 элементов, а не массив ССЫЛОК: int & a[3]

Так же ссылки можно использовать для передачи больших параметров, передача которых мождет оказаться дорогой операцией (vector, set, map), что бы передать такой параметр, но запретить его изменять внутри функции, стоит использовать константную ссылку:

// const string &s эквивалентно string const &s
int foo(string const &s) {}