Информация об изменениях

Сообщение Re: Не могу понять ссылки в C++ от 15.06.2024 6:28

Изменено 15.06.2024 6:41 andrey.desman

Re: Не могу понять ссылки в C++
Здравствуйте, Worminator X, Вы писали:

WX>Из учебника Столярова так и не понял, что такое есть ссылка в C++ (вроде синтаксический сахар над указателем, но как-то странно и непонятно работает).


Ссылки — это не синтаксический сахар над указателями. По крайней мере не в смысле определения, хотя их и можно использовать как таковые.
Саму ссылку нельзя изменить, она привязывается (bind) к чему-то в момент определения и остается привязанной к этому выражению до конца своего существования.
Грубо говоря ссылка — это разыменованный константный указатель. Не указатель на константу, а именно константный указатель. А, например, константная ссылка — это константный указатель на константу (const * const).

В твоем коде
    for (
        List &current = (List&)list;
        current.getType() == LT_INTEGER_LIST;
        current = ((IntegerList&)current).getNext()
    )


создается ссылка current, которая привязывается к list. Она и останется привязанной к list.
Выражение current = ((IntegerList&)current).getNext() не перепривязывает ссылку current к другому объекту, а присваивает привязанному объекту list значение из list.getNext().

Если на указателях, то это эквивалентно следующему:
    for (
        List* const current = &(List&)list; // сам указатель константный, его нельзя поменять, как и привязку ссылки
        current->getType() == LT_INTEGER_LIST;
        *current = *((IntegerList*)current)->getNext() // считаем, что getNext() тоже указатель возвращает, как во втором варианте
    )


На ссылках такой цикл написать в принципе невозможно.

WX>По правилам хорошего тона в C++ нужно всегда использовать ссылки вместо указателей (кроме интеграции с кодом на Си) и по возможности не управлять памятью вручную, т.к. это чревато утечками.


Нет такого правила. Всему свое применение.

WX>Я попытался переписать код, и вышло примерно следующее (для упрощения пока без шаблонов):


Как раз здесь ссылки неуместны, если ты конечно не строишь иммутабельные списки. Если потребуется список поменять, его придется перестроить весь, от начала и до конца.
При этом, с динамической памятью на ссылках работать не получится.

Что касается управления памятью вручную, то да. Вместо обычных указателей здесь лучше использовать умный std::unique_ptr.

WX>И еще, почему, если в EmptyList сделать пустой конструктор, то на строки const EmptyList empty(); const IntegerList n3(3, empty); компилятор ругается? Вариант с const EmptyList empty(void) не помог.


const EmptyList empty(); — это объявление функции empty(), возвращающей const EmptyList. Смотри https://en.wikipedia.org/wiki/Most_vexing_parse
Исправить можно инициализатором фигурными скобками const EmptyList empty{};. Или вообще их отсутствием (все равно будет вызван конструктор по умолчанию).
Re: Не могу понять ссылки в C++
Здравствуйте, Worminator X, Вы писали:

WX>Из учебника Столярова так и не понял, что такое есть ссылка в C++ (вроде синтаксический сахар над указателем, но как-то странно и непонятно работает).


Ссылки — это не совсемсинтаксический сахар над указателями. По крайней мере не в смысле определения, хотя их и можно использовать как таковые. Ссылка — это альтернативное имя для какого-то выражения, алиас.
Саму ссылку нельзя изменить, она привязывается (bind) к чему-то в момент определения и остается привязанной к этому выражению до конца своего существования.
Грубо говоря, ссылка ведет себя как разыменованный константный указатель. Не указатель на константу, а именно константный указатель. А, например, константная ссылка как константный указатель на константу (const * const).

// ссылка
int a[5][5];
int& b = a[1][1];
b = 10; // эквивалентно a[1][1] = 10
b = a[0][0]; // эквивалентно a[1][1] = a[0][0], перепривязки здесь нет
b = 12; // снова эквивалентно a[1][1] = 12

// аналогично на указателях
int a[5][5];
int* const b = &a[1][1];
*b = 10; // эквивалентно a[1][1] = 10
*b = a[0][0]; // эквивалентно a[1][1] = a[0][0], перепривязки здесь нет
*b = 12; // снова эквивалентно a[1][1] = 12
// b = &a[0][0]; // нельзя, сам указатель b константный, его не перепривязать, как и ссылку


// константная ссылка
int a[5][5];
const int& b = a[1][1];
a[0][0] = b; // эквивалентно a[0][0] = a[1][1]
// b = ...; // нельзя

// аналогично на указателях
int a[5][5];
int const* const b = &a[1][1];
a[0][0] = *b; // эквивалентно a[0][0] = a[1][1]
// *b = ...; // нельзя
// b = ...; // нельзя


В твоем коде
    for (
        List &current = (List&)list;
        current.getType() == LT_INTEGER_LIST;
        current = ((IntegerList&)current).getNext()
    )


создается ссылка current, которая привязывается к list. Она и останется привязанной к list.
Выражение current = ((IntegerList&)current).getNext() не перепривязывает ссылку current к другому объекту, а присваивает привязанному объекту list значение из list.getNext().

Если на указателях, то это эквивалентно следующему:
    for (
        List* const current = &(List&)list; // сам указатель константный, его нельзя поменять, как и привязку ссылки
        current->getType() == LT_INTEGER_LIST;
        *current = *((IntegerList*)current)->getNext() // считаем, что getNext() тоже указатель возвращает, как во втором варианте
    )


На ссылках такой цикл написать в принципе невозможно.

WX>По правилам хорошего тона в C++ нужно всегда использовать ссылки вместо указателей (кроме интеграции с кодом на Си) и по возможности не управлять памятью вручную, т.к. это чревато утечками.


Нет такого правила. Всему свое применение.

WX>Я попытался переписать код, и вышло примерно следующее (для упрощения пока без шаблонов):


Как раз здесь ссылки неуместны, если ты конечно не строишь иммутабельные списки. Если потребуется список поменять, его придется перестроить весь, от начала и до конца.
При этом, с динамической памятью на ссылках работать не получится.

Что касается управления памятью вручную, то да. Вместо обычных указателей здесь лучше использовать умный std::unique_ptr.

WX>И еще, почему, если в EmptyList сделать пустой конструктор, то на строки const EmptyList empty(); const IntegerList n3(3, empty); компилятор ругается? Вариант с const EmptyList empty(void) не помог.


const EmptyList empty(); — это объявление функции empty(), возвращающей const EmptyList. Смотри https://en.wikipedia.org/wiki/Most_vexing_parse
Исправить можно инициализатором фигурными скобками const EmptyList empty{};. Или вообще их отсутствием (все равно будет вызван конструктор по умолчанию).