Сообщение Re: Не могу понять ссылки в C++ от 15.06.2024 10:00
Изменено 15.06.2024 11:27 rg45
Re: Не могу понять ссылки в C++
Здравствуйте, Worminator X, Вы писали:
WX>Несколько раз пытался освоить C++, и всегда забрасывал это дело — сложный, совершенно непонятный язык.
WX>Вот, допустим, совершенно тривиальный код обхода списка целых чисел на чистом Си с указателями:
WX>Можно переписать его в стиле "Си с классами", здесь пока тоже все очевидно, просто теперь new/delete вместо malloc/free и class вместо struct:
WX>Примерно так я представлял себе C++ в школе и универе, не зная ни про ссылки, ни про операторы, ни про шаблоны и имея смутное представление о множественном наследовании.
WX>Т.к. Pure C, Turbo Pascal и Delphi (и чуть позже Java с C#) решали все необходимые задачи, изучать C++ тогда не было никакой необходимости.
WX>По правилам хорошего тона в C++ нужно всегда использовать ссылки вместо указателей (кроме интеграции с кодом на Си) и по возможности не управлять памятью вручную, т.к. это чревато утечками.
WX>Я попытался переписать код, и вышло примерно следующее (для упрощения пока без шаблонов):
WX>Помимо того, что этот талмуд совершенно непонятен, оно еще и не работает.
WX>Рекурсивный обход списка в recursivePrintIntegerList вроде выводит все как положено, но в foreachPrintIntegerList цикл почему-то выводит одни единицы и не хочет переходить на следующий элемент.
WX>Что нужно исправить? И что можно почитать по теме?
WX>Из учебника Столярова так и не понял, что такое есть ссылка в C++ (вроде синтаксический сахар над указателем, но как-то странно и непонятно работает).
WX>И еще, почему, если в EmptyList сделать пустой конструктор, то на строки const EmptyList empty(); const IntegerList n3(3, empty); компилятор ругается? Вариант с const EmptyList empty(void) не помог.
Я бы начал с того, что нет в С++ такого правила, которое запрещало бы использование сырых указателей. Плохо, когда владение объектом осуществляется через сырой указатель — это действительно чревато и утечками памяти, и риском влететь на неопределенное поведение в случае повторного удаления объекта. Также плохо, когда указатель используется в качестве ссылки на обязательный объект, который не может отсутсвовать. Вообще плохо, когда сырые указатели используются бездумно, просто потому, что программист не видит особой разницы между ссылкой и указателем. В то же время есть случаи, когда использование сырых указателей вполне оправдано — например, в качестве итераторов последовательностей, занимающих непрерывные области в памяти.
Идем далее. Современный С++ заточен, в первую очередь, под решение практических задач. На выбранном примере трудно увидеть преимущества С++ как раз ввиду того, что практическая ценность данного примера стремится к нулю. Суди сам: твой список заточен под конкретный тип данных и у тебя (в рамках данного примера) даже не предусмотрено даже сколько-нибудь общего способа наполнить список произвольными данными. Как только ты захочешь исправить этот недостаток, твой код тут же начнет раздуваться и обрастать костылями, подобными print_integer_list и new_integer_list. Короче говоря, твой оригинальный пример на С лишь создает иллюзию простоты в виду своей полной практической бесполезности.
Идем дальше, твой пример на C иллюстрирует следующую большую проблему — необходимость ручного управления ресурсами. Программист, разрабатывавший класс списка тупо переложил ответственность за освобождение ресурсов на пользователя. Теперь остается только молиться на то, что пользователь вызовет free_integer_list в нужное время, в нужном месте и сделает это ровно один раз. Когда вся программа строится по такому принципу, вот тогда и получаются традиционные для С проблемы: утечки памяти, крэши и неопределенное поведение.
Что еще хочется отметить — абсолютно необоснованное использование полиморфизма времени выполнения — очень распространненная болячка. Мне доводилось видеть много примеров, когда ран-тайм полиморфизм применялся не потому, что он реально нужен, а только потому, что "так научили" и программист просто не знает, как можно это сделать по-другому.
Ну и в качестве вишенки на торте, распростаненный стереотип — "я хотел сделать проще, поэтому без шаблонов". По факту же все наоборот.
Тут можно было бы еще наковырять по мелочам, но, думаю, того, что сказано пока достаточно. В качестве иллюстрации сказанного можно предложить вот такой пример реализации односвязного списка на C++:
http://coliru.stacked-crooked.com/a/33377b09832a0bd3
Эта реализация очень груба и местами даже наивна. И вообще она не нужна, поскольку в стандартной библиотеке есть тот же std::list и другие контейнеры. Тем не менее, даже такая реализация демонстрирует возможности и преимущества C++ по всем критериям — и по простоте и наглядности кода, и по обобщенности.
Для наглядности можно посмотреть на использование:
сравни это:
и это:
Какой вариант проще для восприятия? Прими также во внимание, что я прямо в имеющейся реализации могу использовать произвольные типы данных: числа, строки, стандартные и пользовательские классы. И что пришлось бы сделать тебе, чтобы добиться сопоставимой функциональности.
WX>Несколько раз пытался освоить C++, и всегда забрасывал это дело — сложный, совершенно непонятный язык.
WX>Вот, допустим, совершенно тривиальный код обхода списка целых чисел на чистом Си с указателями:
WX>Можно переписать его в стиле "Си с классами", здесь пока тоже все очевидно, просто теперь new/delete вместо malloc/free и class вместо struct:
WX>Примерно так я представлял себе C++ в школе и универе, не зная ни про ссылки, ни про операторы, ни про шаблоны и имея смутное представление о множественном наследовании.
WX>Т.к. Pure C, Turbo Pascal и Delphi (и чуть позже Java с C#) решали все необходимые задачи, изучать C++ тогда не было никакой необходимости.
WX>По правилам хорошего тона в C++ нужно всегда использовать ссылки вместо указателей (кроме интеграции с кодом на Си) и по возможности не управлять памятью вручную, т.к. это чревато утечками.
WX>Я попытался переписать код, и вышло примерно следующее (для упрощения пока без шаблонов):
WX>Помимо того, что этот талмуд совершенно непонятен, оно еще и не работает.
WX>Рекурсивный обход списка в recursivePrintIntegerList вроде выводит все как положено, но в foreachPrintIntegerList цикл почему-то выводит одни единицы и не хочет переходить на следующий элемент.
WX>Что нужно исправить? И что можно почитать по теме?
WX>Из учебника Столярова так и не понял, что такое есть ссылка в C++ (вроде синтаксический сахар над указателем, но как-то странно и непонятно работает).
WX>И еще, почему, если в EmptyList сделать пустой конструктор, то на строки const EmptyList empty(); const IntegerList n3(3, empty); компилятор ругается? Вариант с const EmptyList empty(void) не помог.
Я бы начал с того, что нет в С++ такого правила, которое запрещало бы использование сырых указателей. Плохо, когда владение объектом осуществляется через сырой указатель — это действительно чревато и утечками памяти, и риском влететь на неопределенное поведение в случае повторного удаления объекта. Также плохо, когда указатель используется в качестве ссылки на обязательный объект, который не может отсутсвовать. Вообще плохо, когда сырые указатели используются бездумно, просто потому, что программист не видит особой разницы между ссылкой и указателем. В то же время есть случаи, когда использование сырых указателей вполне оправдано — например, в качестве итераторов последовательностей, занимающих непрерывные области в памяти.
Идем далее. Современный С++ заточен, в первую очередь, под решение практических задач. На выбранном примере трудно увидеть преимущества С++ как раз ввиду того, что практическая ценность данного примера стремится к нулю. Суди сам: твой список заточен под конкретный тип данных и у тебя (в рамках данного примера) даже не предусмотрено даже сколько-нибудь общего способа наполнить список произвольными данными. Как только ты захочешь исправить этот недостаток, твой код тут же начнет раздуваться и обрастать костылями, подобными print_integer_list и new_integer_list. Короче говоря, твой оригинальный пример на С лишь создает иллюзию простоты в виду своей полной практической бесполезности.
Идем дальше, твой пример на C иллюстрирует следующую большую проблему — необходимость ручного управления ресурсами. Программист, разрабатывавший класс списка тупо переложил ответственность за освобождение ресурсов на пользователя. Теперь остается только молиться на то, что пользователь вызовет free_integer_list в нужное время, в нужном месте и сделает это ровно один раз. Когда вся программа строится по такому принципу, вот тогда и получаются традиционные для С проблемы: утечки памяти, крэши и неопределенное поведение.
Что еще хочется отметить — абсолютно необоснованное использование полиморфизма времени выполнения — очень распространненная болячка. Мне доводилось видеть много примеров, когда ран-тайм полиморфизм применялся не потому, что он реально нужен, а только потому, что "так научили" и программист просто не знает, как можно это сделать по-другому.
Ну и в качестве вишенки на торте, распростаненный стереотип — "я хотел сделать проще, поэтому без шаблонов". По факту же все наоборот.
Тут можно было бы еще наковырять по мелочам, но, думаю, того, что сказано пока достаточно. В качестве иллюстрации сказанного можно предложить вот такой пример реализации односвязного списка на C++:
http://coliru.stacked-crooked.com/a/33377b09832a0bd3
UniDirList, C++ | |
| |
Эта реализация очень груба и местами даже наивна. И вообще она не нужна, поскольку в стандартной библиотеке есть тот же std::list и другие контейнеры. Тем не менее, даже такая реализация демонстрирует возможности и преимущества C++ по всем критериям — и по простоте и наглядности кода, и по обобщенности.
Для наглядности можно посмотреть на использование:
сравни это:
int main()
{
for (double value : UniDirList{3.14, 2.71, 1.61})
{
std::cout << value << " ";
}
}
и это:
int main(int argc, char *argv[]) {
struct INTEGER_LIST *numbers;
numbers = new_integer_list(1, 3);
if (!numbers) return 1;
print_integer_list(numbers);
free_integer_list(numbers);
return 0;
}
Какой вариант проще для восприятия? Прими также во внимание, что я прямо в имеющейся реализации могу использовать произвольные типы данных: числа, строки, стандартные и пользовательские классы. И что пришлось бы сделать тебе, чтобы добиться сопоставимой функциональности.
Re: Не могу понять ссылки в C++
Здравствуйте, Worminator X, Вы писали:
WX>Несколько раз пытался освоить C++, и всегда забрасывал это дело — сложный, совершенно непонятный язык.
WX>Вот, допустим, совершенно тривиальный код обхода списка целых чисел на чистом Си с указателями:
WX>Можно переписать его в стиле "Си с классами", здесь пока тоже все очевидно, просто теперь new/delete вместо malloc/free и class вместо struct:
WX>Примерно так я представлял себе C++ в школе и универе, не зная ни про ссылки, ни про операторы, ни про шаблоны и имея смутное представление о множественном наследовании.
WX>Т.к. Pure C, Turbo Pascal и Delphi (и чуть позже Java с C#) решали все необходимые задачи, изучать C++ тогда не было никакой необходимости.
WX>По правилам хорошего тона в C++ нужно всегда использовать ссылки вместо указателей (кроме интеграции с кодом на Си) и по возможности не управлять памятью вручную, т.к. это чревато утечками.
WX>Я попытался переписать код, и вышло примерно следующее (для упрощения пока без шаблонов):
WX>Помимо того, что этот талмуд совершенно непонятен, оно еще и не работает.
WX>Рекурсивный обход списка в recursivePrintIntegerList вроде выводит все как положено, но в foreachPrintIntegerList цикл почему-то выводит одни единицы и не хочет переходить на следующий элемент.
WX>Что нужно исправить? И что можно почитать по теме?
WX>Из учебника Столярова так и не понял, что такое есть ссылка в C++ (вроде синтаксический сахар над указателем, но как-то странно и непонятно работает).
WX>И еще, почему, если в EmptyList сделать пустой конструктор, то на строки const EmptyList empty(); const IntegerList n3(3, empty); компилятор ругается? Вариант с const EmptyList empty(void) не помог.
Я бы начал с того, что нет в С++ такого правила, которое запрещало бы использование сырых указателей. Плохо, когда владение объектом осуществляется через сырой указатель — это действительно чревато и утечками памяти, и риском влететь на неопределенное поведение в случае повторного удаления объекта. Также плохо, когда указатель используется в качестве ссылки на обязательный объект, который не может отсутсвовать. Вообще плохо, когда сырые указатели используются бездумно, просто потому, что программист не видит особой разницы между ссылкой и указателем. В то же время есть случаи, когда использование сырых указателей вполне оправдано — например, в качестве итераторов последовательностей, занимающих непрерывные области в памяти.
Идем далее. Современный С++ заточен, в первую очередь, под решение практических задач. На выбранном примере трудно увидеть преимущества С++ как раз ввиду того, что практическая ценность данного примера стремится к нулю. Суди сам: твой список заточен под конкретный тип данных и у тебя (в рамках данного примера) даже не предусмотрено даже сколько-нибудь общего способа наполнить список произвольными данными. Как только ты захочешь исправить этот недостаток, твой код тут же начнет раздуваться и обрастать костылями, подобными print_integer_list и new_integer_list. Короче говоря, твой оригинальный пример на С лишь создает иллюзию простоты в виду своей полной практической бесполезности.
Идем дальше, твой пример на C иллюстрирует следующую большую проблему — необходимость ручного управления ресурсами. Программист, разрабатывавший класс списка тупо переложил ответственность за освобождение ресурсов на пользователя. Теперь остается только молиться на то, что пользователь вызовет free_integer_list в нужное время, в нужном месте и сделает это ровно один раз. Когда вся программа строится по такому принципу, вот тогда и получаются традиционные для С проблемы: утечки памяти, крэши и неопределенное поведение.
Что еще хочется отметить — абсолютно необоснованное использование полиморфизма времени выполнения — очень распространненная болячка. Мне доводилось видеть много примеров, когда ран-тайм полиморфизм применялся не потому, что он реально нужен, а только потому, что "так научили" и программист просто не знает, как можно это сделать по-другому.
Еще одна твоя ошибка в том, что ты пытаешься перенести эту проблемную реализацию с С на С++ один-в-один со всеми ее проблемами. Конечно же ничего хорошего из этого не получится. Это все равно, что залить керосин в электрическую лампочку и удивляться, почему не светит. При работе с С++ в принципе другой подход нужен.
Ну и как вишенка на торте, распростаненный стереотип — "я хотел сделать проще, поэтому без шаблонов". По факту же все наоборот.
В качестве иллюстрации сказанного можно предложить вот такой пример реализации односвязного списка на C++:
http://coliru.stacked-crooked.com/a/33377b09832a0bd3
Эта реализация очень груба и местами даже наивна. И вообще она не нужна, поскольку в стандартной библиотеке есть тот же std::list и другие контейнеры. Тем не менее, даже такая реализация демонстрирует возможности и преимущества C++ по всем критериям — и по простоте и наглядности кода, и по обобщенности.
Для наглядности можно посмотреть на использование:
сравни это:
и это:
Какой вариант проще для восприятия? Прими также во внимание, что я прямо в имеющейся реализации могу использовать произвольные типы данных: числа, строки, стандартные и пользовательские классы. И что пришлось бы сделать тебе, чтобы добиться сопоставимой функциональности.
WX>Несколько раз пытался освоить C++, и всегда забрасывал это дело — сложный, совершенно непонятный язык.
WX>Вот, допустим, совершенно тривиальный код обхода списка целых чисел на чистом Си с указателями:
WX>Можно переписать его в стиле "Си с классами", здесь пока тоже все очевидно, просто теперь new/delete вместо malloc/free и class вместо struct:
WX>Примерно так я представлял себе C++ в школе и универе, не зная ни про ссылки, ни про операторы, ни про шаблоны и имея смутное представление о множественном наследовании.
WX>Т.к. Pure C, Turbo Pascal и Delphi (и чуть позже Java с C#) решали все необходимые задачи, изучать C++ тогда не было никакой необходимости.
WX>По правилам хорошего тона в C++ нужно всегда использовать ссылки вместо указателей (кроме интеграции с кодом на Си) и по возможности не управлять памятью вручную, т.к. это чревато утечками.
WX>Я попытался переписать код, и вышло примерно следующее (для упрощения пока без шаблонов):
WX>Помимо того, что этот талмуд совершенно непонятен, оно еще и не работает.
WX>Рекурсивный обход списка в recursivePrintIntegerList вроде выводит все как положено, но в foreachPrintIntegerList цикл почему-то выводит одни единицы и не хочет переходить на следующий элемент.
WX>Что нужно исправить? И что можно почитать по теме?
WX>Из учебника Столярова так и не понял, что такое есть ссылка в C++ (вроде синтаксический сахар над указателем, но как-то странно и непонятно работает).
WX>И еще, почему, если в EmptyList сделать пустой конструктор, то на строки const EmptyList empty(); const IntegerList n3(3, empty); компилятор ругается? Вариант с const EmptyList empty(void) не помог.
Я бы начал с того, что нет в С++ такого правила, которое запрещало бы использование сырых указателей. Плохо, когда владение объектом осуществляется через сырой указатель — это действительно чревато и утечками памяти, и риском влететь на неопределенное поведение в случае повторного удаления объекта. Также плохо, когда указатель используется в качестве ссылки на обязательный объект, который не может отсутсвовать. Вообще плохо, когда сырые указатели используются бездумно, просто потому, что программист не видит особой разницы между ссылкой и указателем. В то же время есть случаи, когда использование сырых указателей вполне оправдано — например, в качестве итераторов последовательностей, занимающих непрерывные области в памяти.
Идем далее. Современный С++ заточен, в первую очередь, под решение практических задач. На выбранном примере трудно увидеть преимущества С++ как раз ввиду того, что практическая ценность данного примера стремится к нулю. Суди сам: твой список заточен под конкретный тип данных и у тебя (в рамках данного примера) даже не предусмотрено даже сколько-нибудь общего способа наполнить список произвольными данными. Как только ты захочешь исправить этот недостаток, твой код тут же начнет раздуваться и обрастать костылями, подобными print_integer_list и new_integer_list. Короче говоря, твой оригинальный пример на С лишь создает иллюзию простоты в виду своей полной практической бесполезности.
Идем дальше, твой пример на C иллюстрирует следующую большую проблему — необходимость ручного управления ресурсами. Программист, разрабатывавший класс списка тупо переложил ответственность за освобождение ресурсов на пользователя. Теперь остается только молиться на то, что пользователь вызовет free_integer_list в нужное время, в нужном месте и сделает это ровно один раз. Когда вся программа строится по такому принципу, вот тогда и получаются традиционные для С проблемы: утечки памяти, крэши и неопределенное поведение.
Что еще хочется отметить — абсолютно необоснованное использование полиморфизма времени выполнения — очень распространненная болячка. Мне доводилось видеть много примеров, когда ран-тайм полиморфизм применялся не потому, что он реально нужен, а только потому, что "так научили" и программист просто не знает, как можно это сделать по-другому.
Еще одна твоя ошибка в том, что ты пытаешься перенести эту проблемную реализацию с С на С++ один-в-один со всеми ее проблемами. Конечно же ничего хорошего из этого не получится. Это все равно, что залить керосин в электрическую лампочку и удивляться, почему не светит. При работе с С++ в принципе другой подход нужен.
Ну и как вишенка на торте, распростаненный стереотип — "я хотел сделать проще, поэтому без шаблонов". По факту же все наоборот.
В качестве иллюстрации сказанного можно предложить вот такой пример реализации односвязного списка на C++:
http://coliru.stacked-crooked.com/a/33377b09832a0bd3
UniDirList, C++ | |
| |
Эта реализация очень груба и местами даже наивна. И вообще она не нужна, поскольку в стандартной библиотеке есть тот же std::list и другие контейнеры. Тем не менее, даже такая реализация демонстрирует возможности и преимущества C++ по всем критериям — и по простоте и наглядности кода, и по обобщенности.
Для наглядности можно посмотреть на использование:
сравни это:
int main()
{
for (double value : UniDirList{3.14, 2.71, 1.61})
{
std::cout << value << " ";
}
}
и это:
int main(int argc, char *argv[]) {
struct INTEGER_LIST *numbers;
numbers = new_integer_list(1, 3);
if (!numbers) return 1;
print_integer_list(numbers);
free_integer_list(numbers);
return 0;
}
Какой вариант проще для восприятия? Прими также во внимание, что я прямо в имеющейся реализации могу использовать произвольные типы данных: числа, строки, стандартные и пользовательские классы. И что пришлось бы сделать тебе, чтобы добиться сопоставимой функциональности.