После прочтения этой знаменитой речи Линуса Торвальдса я подумал, что на самом деле являются подводными камнями для программистов на C ++. Я явно не имею в виду опечатки или неверный поток программ, которые рассматриваются в этом вопросе и его ответах , но я обращаюсь к более высокоуровневым ошибкам, которые не обнаруживаются компилятором и не приводят к очевидным ошибкам при первом запуске, полным ошибкам проектирования, вещи, которые невероятны в C, но, вероятно, будут сделаны в C ++ новичками, которые не понимают всех последствий своего кода.
Я также приветствую ответы, указывающие на значительное снижение производительности там, где это обычно не ожидается. Пример того, что один из моих профессоров однажды рассказал мне о генераторе парсера LR (1), который я написал:
Вы использовали слишком много случаев ненужного наследования и виртуальности. Наследование делает проект намного более сложным (и неэффективным из-за подсистемы RTTI (вывод типа времени выполнения)), и поэтому его следует использовать только там, где это имеет смысл, например, для действий в таблице разбора. Поскольку вы интенсивно используете шаблоны, вам практически не нужно наследование. "
virtual
функций, верно?dynamic_cast
должен ли a быть успешным или нет, и несколько других вещей, но рефлексия охватывает гораздо больше, в том числе возможность извлекать информацию об атрибутах или функциях члена, что не является присутствует в C ++.Ответы:
Торвальдс говорит из своей задницы здесь.
Хорошо, почему он говорит из своей задницы:
Прежде всего, его напыщенная речь - действительно ничто, но напыщенная речь. Здесь очень мало актуального контента. Единственная причина, по которой он действительно известен или даже слегка уважаем, заключается в том, что он создан Богом Linux. Его главный аргумент в том, что C ++ - дерьмо, и он любит злить людей на C ++. Конечно, нет никакой причины отвечать на это, и любой, кто считает это разумным аргументом, в любом случае не подлежит обсуждению.
Что касается того, что могло бы прозвучать как его самые объективные моменты:
По сути, Торвальдс говорит из своей задницы. Там нет внятного аргумента о чем-либо. Ожидать серьезного опровержения такой ерунды просто глупо. Мне говорят «расширить» на опровержение чего-то, что я должен был бы расширить, если бы я сказал это. Если вы действительно, честно посмотрите на то, что сказал Торвальдс, вы увидите, что он на самом деле ничего не сказал.
То, что Бог говорит, что это не означает, что это имеет какой-то смысл или должно восприниматься более серьезно, чем если бы это сказал какой-то случайный бозо. По правде говоря, Бог - это просто еще один случайный бозо.
Отвечая на актуальный вопрос:
Вероятно, самая плохая и самая распространенная, плохая практика C ++ - это обращаться с ней как с C. Продолжение использования функций API C, таких как printf, gets (также считается плохим в C), strtok и т. Д., Не только не позволяет использовать предоставленную мощность из-за более жесткой системы типов они неизбежно приводят к дальнейшим осложнениям при попытке взаимодействия с «реальным» кодом C ++. Так что, в принципе, делайте прямо противоположное тому, что советует Торвальдс.
Научитесь использовать STL и Boost для дальнейшего обнаружения ошибок во время компиляции и для упрощения своей жизни другими общими способами (например, токенайзер Boost является как безопасным, так и лучшим интерфейсом). Это правда, что вам нужно научиться читать ошибки шаблона, что поначалу устрашает, но (по моему опыту в любом случае), откровенно говоря, намного проще, чем пытаться отлаживать что-то, что генерирует неопределенное поведение во время выполнения, что делает C api довольно легко сделать.
Не скажу, что С не так хорош. Мне конечно больше нравится C ++. C программисты любят C лучше. Есть компромиссы и субъективные лайки в игре. Там также много дезинформации и FUD, плавающих вокруг. Я бы сказал, что в C ++ все больше и больше FUD и дезинформации, но в этом отношении я предвзят. Например, проблемы «раздувания» и «производительности», которые, предположительно, есть у C ++, на самом деле не являются серьезными проблемами в большинстве случаев и, безусловно, выходят за рамки реальности.
Что касается вопросов, на которые ссылается ваш профессор, они не являются уникальными для C ++. В ООП (и в универсальном программировании) вы хотите предпочесть композицию наследованию. Наследование - это самое сильное из возможных отношений связывания, которое существует во всех языках ОО. C ++ добавляет еще один, более сильный, дружба. Полиморфное наследование должно использоваться для представления абстракций и отношений «есть», оно никогда не должно использоваться для повторного использования. Это вторая по величине ошибка, которую вы можете совершить в C ++, и она довольно большая, но далеко не уникальная для языка. Вы также можете создавать слишком сложные отношения наследования в C # или Java, и у них будут точно такие же проблемы.
источник
Я всегда думал, что опасность C ++ сильно преувеличена неопытным программистом C с программистами.
Да, C ++ сложнее понять, чем что-то вроде Java, но если вы программируете с использованием современных методов, довольно легко написать надежные программы. Я честно не что гораздо сложнее из времени программирования на языке C ++ , чем я в таких языках , как Java, и я часто не хватаю определенные C ++ абстракций , как шаблоны и RAII , когда я проектирую на других языках.
Тем не менее, даже после многих лет программирования на C ++, время от времени я буду делать действительно глупую ошибку, которая была бы невозможна на языке более высокого уровня. Одна распространенная ошибка в C ++ - игнорирование времени жизни объекта: в Java и C # вам, как правило, не нужно заботиться о времени жизни объекта *, потому что все объекты существуют в куче и управляются для вас волшебным сборщиком мусора.
Теперь в современном C ++ обычно вам не нужно особо заботиться о времени жизни объекта. У вас есть деструкторы и умные указатели, которые управляют временем жизни объектов для вас. В 99% случаев это прекрасно работает. Но время от времени вы будете зависать от висящего указателя (или ссылки). Например, совсем недавно у меня был объект (давайте назовем его
Foo
), который содержал внутреннюю переменную ссылки на другой объект (давайте назовем егоBar
). В какой-то момент я тупо расставил вещи так, чтоBar
раньше они выходили за рамкиFoo
, ноFoo
деструктор в итоге вызвал функцию-членBar
. Излишне говорить, что все не очень хорошо.Теперь я не могу винить в этом C ++. Это был мой собственный плохой дизайн, но суть в том, что такого рода вещи не произошли бы на управляемом языке более высокого уровня. Даже при наличии умных указателей и тому подобного вам иногда все же необходимо осознавать время жизни объекта.
* Если управляемый ресурс - это память, то есть.
источник
Разница в коде обычно больше связана с программистом, чем с языком. В частности, хороший программист на C ++ и программист на C придут к одинаково хорошим (даже если иным) решениям. Теперь C - более простой язык (как язык), и это означает, что меньше абстракций и больше видимости того, что на самом деле делает код.
Часть его разглагольствования (он известен своими пристрастиями к C ++) основана на том факте, что все больше людей будут браться за C ++ и писать код, фактически не понимая, что некоторые из абстракций скрывают и делают неправильные предположения.
источник
std::vector<bool>
изменения каждого значения?for ( std::vector<bool>::iterator it = v.begin(), end = v.end(); it != end; ++it ) { *it = !*it; }
? В чем абстрагируется*it = !*it;
?std::vector<bool>
это общеизвестная ошибка, но это действительно хороший пример того, что обсуждается: абстракции хороши, но вы должны быть осторожны с тем, что они скрывают. То же самое может и произойдет в коде пользователя. Начнем с того, что как в C ++, так и в Java я видел людей, использующих исключения для управления потоком, и код, который выглядит как вызов вложенной функции, который на самом деле является средством запуска исключения из программы спасения:void endOperation();
реализовано какthrow EndOperation;
. Хороший программист избежит этих удивительных конструкций, но факт в том, что вы можете их найти.Чрезмерное использование
try/catch
блоков.Обычно это происходит от языков, таких как Java, и люди будут утверждать, что в C ++ отсутствует
finalize
предложение.Но этот код имеет две проблемы:
file
доtry/catch
, потому что вы не можете на самом делеclose
файл, который не существует вcatch
. Это приводит к «утечке объема», котораяfile
видна после закрытия. Вы можете добавить блок, но ...: /return
посреди областиtry
действия, файл не закрывается (вот почему люди недовольны отсутствиемfinalize
предложения)Однако в C ++ у нас есть гораздо более эффективные способы решения этой проблемы, которые:
finalize
using
defer
У нас есть RAII, чье действительно интересное свойство лучше всего суммировать как
SBRM
(Scoped Bound Resources Management).Создавая класс так, чтобы его деструктор очищал ресурсы, которыми он владеет, мы не возлагаем ответственность за управление ресурсом на каждого и каждого его пользователя!
Это особенность , которую я не хватает в любом другом языке, и , вероятно, один, наиболее забыто.
Правда заключается в том, что редко требуется даже написать
try/catch
блок на C ++, кроме верхнего уровня, чтобы избежать завершения без регистрации.источник
fopen
иfclose
здесь.) RAII - это «правильный» способ сделать что-то здесь, но это неудобно для людей, которые хотят использовать библиотеки C из C ++ ,File file("some.txt");
и все (нетopen
, нетclose
, нетtry
...)Одна распространенная ошибка, которая соответствует вашим критериям, заключается в том, что вы не понимаете, как работают конструкторы копирования при работе с выделенной памятью в вашем классе. Я потерял счет времени, потраченного на исправление сбоев или утечек памяти, потому что «нуб» помещал свои объекты в карту или вектор и не писал должным образом конструкторы и деструкторы копирования.
К сожалению, C ++ полон «скрытых» ошибок, подобных этому. Но жаловаться на это все равно, что жаловаться, что вы отправились во Францию и не могли понять, что говорят люди. Если вы собираетесь туда, выучите язык.
источник
C ++ допускает большое разнообразие функций и стилей программирования, но это не значит, что это действительно хорошие способы использования C ++. И на самом деле, невероятно легко неправильно использовать C ++.
Его нужно изучать и понимать правильно , просто изучение на практике (или использование его так, как если бы вы использовали какой-то другой язык) приведет к неэффективному и подверженному ошибкам коду.
источник
Ну ... для начала вы можете прочитать C ++ FAQ Lite
Затем несколько человек построили карьеру, написав книги о тонкостях C ++:
Херб Саттер и Скотт Мейерс, а именно.
Что касается бессмысленной напыщенной речи Торвальдса ... давай на людей, серьезно: ни на одном другом языке не было бы так много чернил, чтобы пролить свет на нюансы этого языка. Все ваши книги по Python, Ruby и Java сосредоточены на написании приложений ... ваши книги на C ++ фокусируются на глупых языковых особенностях / подсказках / ловушках.
источник
Слишком тяжелые шаблоны поначалу могут не привести к ошибкам. Однако со временем люди должны будут изменить этот код, и им будет трудно разобраться в огромном шаблоне. Именно тогда появляются ошибки - непонимание вызывает комментарии «Он компилируется и запускается», что часто приводит к почти-но-не совсем правильному коду.
Вообще, если я вижу, что я делаю трехуровневый общий шаблон, я останавливаюсь и думаю, как его можно сократить до одного. Часто проблема решается путем извлечения функций или классов.
источник
Предупреждение: это не столько ответ, сколько критика разговора, на который в ответе ссылается «пользователь неизвестен».
Его первое главное замечание - это (якобы) «постоянно меняющийся стандарт». В действительности все приведенные им примеры относятся к изменениям в C ++ до того, как появился стандарт. С 1998 года (когда был завершен первый стандарт C ++) изменения в языке были весьма минимальными - на самом деле, многие утверждают, что реальная проблема заключается в том, что нужно было вносить больше изменений. Я достаточно уверен, что весь код, который соответствует исходному стандарту C ++, все еще соответствует текущему стандарту. Хотя это несколько менее определенно, если что-то не изменится быстро (и совершенно неожиданно), то же самое будет в значительной степени верно и с будущим стандартом C ++ (теоретически, весь код, который использовал
export
сломается, но практически не существует; с практической точки зрения это не проблема). Я могу представить несколько других языков, операционных систем (или многое другое, связанное с компьютером), которые могут претендовать на подобные заявления.Затем он входит в «постоянно меняющиеся стили». Опять же, большинство его пунктов довольно близко к чепухе. Он пытается охарактеризовать
for (int i=0; i<n;i++)
как «старый и разоренный» иfor (int i(0); i!=n;++i)
«новый жар». Реальность такова, что хотя существуют типы, для которых такие изменения могут иметь смысл, для нихint
это не имеет значения - и даже когда вы можете что-то получить, это редко необходимо для написания хорошего или правильного кода. Даже в лучшем случае он делает гору из мухи слона.Его следующее утверждение заключается в том, что C ++ «оптимизирует в неправильном направлении», в частности, что, хотя он признает, что использовать хорошие библиотеки легко, он утверждает, что C ++ «делает написание хороших библиотек практически невозможным». Здесь, я считаю, это одна из его самых фундаментальных ошибок. На самом деле, написание хороших библиотек практически для любого языка чрезвычайно сложно. Как минимум, написание хорошей библиотеки требует понимания некоторой проблемной области настолько хорошо, что ваш код работает для множества возможных приложений в этой области (или связанных с ними). Большая часть того, что в действительности делает C ++ , - это «поднять планку» - увидев, насколько лучше может быть библиотека , люди редко желают вернуться к написанию того вида мерзости, который они имели бы в противном случае.действительно хорошие программисты пишут довольно много библиотек, которые могут быть использованы (как он сам признает) «остальными из нас». Это действительно тот случай, когда «это не ошибка, это особенность».
Я не буду пытаться поразить каждую точку по порядку (это заняло бы страницы), но я перейду прямо к его точке закрытия. Он цитирует Бьярне: «Оптимизация всей программы может использоваться для устранения неиспользуемых таблиц виртуальных функций и данных RTTI. Такой анализ особенно подходит для относительно небольших программ, которые не используют динамическое связывание».
Он критикует это, делая неподтвержденное утверждение, что «это действительно сложная проблема», даже если сравнивать ее с проблемой остановки. На самом деле, ничего подобного - на самом деле это сделал компоновщик, включенный в Zortech C ++ (в значительной степени первый компилятор C ++ для MS-DOS еще в 1980-х годах). Это правда, что трудно быть уверенным в том, что каждый бит, возможно, посторонних данных был удален, но все же вполне разумно делать довольно честную работу.
Независимо от этого, однако, гораздо более важным моментом является то, что в любом случае это совершенно не имеет значения для большинства программистов. Как известно тем из нас, кто разбирал довольно много кода, если вы не пишете на языке ассемблера вообще без библиотек, ваши исполняемые файлы почти наверняка содержат достаточное количество «материала» (как кода, так и данных, в типичных случаях), который вы вероятно, даже не знаю о, не говоря уже о том, чтобы на самом деле использовать. Для большинства людей в большинстве случаев это не имеет значения - если вы не разрабатываете для самых маленьких встроенных систем, то дополнительное потребление памяти просто не имеет значения.
В конце концов, это правда, что эта напыщенная речь имеет немного больше субстанции, чем идиотизм Линуса - но это дает ему именно проклятие со слабой похвалой, которого оно заслуживает.
источник
Как программист на C, которому приходилось кодировать на C ++ из-за неизбежных обстоятельств, вот мой опыт. Я очень мало использую C ++ и в основном придерживаюсь C. Основная причина в том, что я не очень хорошо понимаю C ++. У меня не было / не было наставника, чтобы показать мне тонкости C ++ и как написать хороший код в нем. И без руководства из очень-очень хорошего кода на C ++ очень сложно написать хороший код на C ++. ИМХО, это самый большой недостаток C ++, потому что трудно найти хороших программистов на C ++, желающих поддерживать начинающих.
Некоторые хиты производительности, которые я видел, обычно связаны с магическим распределением памяти в STL (да, вы можете изменить распределитель, но кто это делает, когда он начинает с C ++?). Специалисты C ++ обычно слышат аргументы о том, что векторы и массивы имеют одинаковую производительность, потому что векторы используют массивы внутренне, а абстракция суперэффективна. Я обнаружил, что это верно на практике для векторного доступа и изменения существующих значений. Но это не так для добавления новой записи, построения и уничтожения векторов. gprof показал, что в совокупности 25% времени для приложения было потрачено на конструкторы векторов, деструкторы, memmove (для перемещения всего вектора для добавления нового элемента) и другие перегруженные операторы векторов (например, ++).
В том же приложении векторthingSmall использовался для представления чего-то большого. Не было необходимости произвольного доступа к чему-то маленькому в чем-то большом. Тем не менее вместо списка использовался вектор. Причина, почему вектор был использован? Потому что оригинальный кодер был знаком с массивом, подобным синтаксису векторов, и не очень знаком с итераторами, необходимыми для списков (да, он из C-фона). Продолжаем доказывать, что для правильного понимания C ++ требуется много рекомендаций от экспертов. C предлагает так мало базовых конструкций без каких-либо абстракций, что вы можете сделать это намного проще, чем C ++.
источник
Хотя мне нравится Линус Торвальдс, эта напыщенная речь бессмысленна - просто напыщенная речь.
Если вам нравится смотреть подробные заявления, вот один из них: «Почему C ++ вреден для окружающей среды, вызывает глобальное потепление и убивает щенков» http://chaosradio.ccc.de/camp2007_m4v_1951.html Дополнительные материалы: http: // www .fefe.de / C ++ /
Занимательный разговор, imho
источник
STL и boost являются переносимыми, на уровне исходного кода. Я думаю, о чем говорит Линус, так это о том, что в C ++ отсутствует ABI (двоичный интерфейс приложения). Поэтому вам нужно скомпилировать все библиотеки, с которыми вы связываетесь, с той же версией компилятора и с теми же ключами, либо ограничить себя C ABI в границах dll. Я также нахожу это раздражающим ... но если вы не создаете сторонние библиотеки, вы должны иметь возможность контролировать свою среду сборки. Я считаю, что ограничивать себя в C ABI не стоит. Удобство возможности передавать строки, векторы и интеллектуальные указатели из одной библиотеки DLL в другую стоит того, чтобы перестраивать все библиотеки при обновлении компиляторов или переключателей компилятора. Золотые правила, которым я следую:
Наследовать, чтобы повторно использовать интерфейс, а не реализацию
-Предпочтение агрегации по наследованию
-Предпочитайте, где это возможно, свободные функции для методов-членов.
-Всегда используйте идиому RAII, чтобы сделать ваш код строго исключительным. Избегайте попытки поймать.
-Используйте умные указатели, избегайте голых (не принадлежащих) указателей
-Предпочитайте семантику значений для ссылки
-Не изобретай велосипед заново, используй stl и boost
-Используйте идиому Pimpl, чтобы скрыть приватный и / или обеспечить брандмауэр компилятора
источник
Не ставить финал
;
в конце клауз-декларации, по крайней мере, в некоторых версиях VC.источник