Нестандартный С++
КРИС КАСПЕРСКИ АКА МЫЩЪХ
🕛 02.11.2006, 16:27
священные войныВокруг языков программирования давно разгораются битвы, но все как-то мимо писсуара и совсем не в тему. Обычный тезис: «Покажите мне пример, который нельзя реализовать на моем любимом XYZ, и тогда я съем свою тюбетейку». Еще бы козырек намазать маслом, чтобы было легче глотать!
Теоретически, если задачу можно решить на Машине Тьюринга, ее можно запрограммировать на любом существующем языке. Весь вопрос в том, за какое время, какой ценой и с какой эффективностью. Чистый язык сам по себе никому не интересен. Для полноценной работы программисту требуются средства разработки: трансляторы, линкеры, верификаторы, IDE, отладчики, библиотеки и т.п. Причем желательно, чтобы трансляторов было больше одного.
Основная разница между С++ и Basic'ом вовсе не в том, что трансляторы Basic'а генерируют тормозной код. Главное отличие С++ в том, что он никому не принадлежит. Есть открытый стандарт и десятки аттестованных компиляторов, которые генерируют стандартный промежуточный код, легко интегрируемый в любой проект, написанный, например, на Паскале или Ассемблере. Это не только упрощает перенос на другие платформы (LINUX, Palm OS), но и стабилизирует обстановку на рынке.
Язык С++ нельзя просто взять и «свернуть», как Microsoft свернула Visual Basic, отправив в игнор совместимость с ранее написанным кодом. Десятки тысяч программистов оказались буквально выброшенными на улицу. Переделывать отлаженный код под .NET муторно, сложно, и нет никакой гарантии, что через несколько лет Microsoft еще раз не впадет в маразм. Переучиваться под что-то другое слишком поздно. Как показывает практика, Basic необратимо калечит образ мышления программиста, особенно Visual. Кстати, Basic тоже имеет свой стандарт, как европейский, так и американский. Однако возможности настолько скромны, что даже самая непривередливая девушка не возьмет его замуж.
выбирая язык
ты выбираешь судьбу. И чтобы эта судьба не зависела от воли левой пятки Microsoft или Borland, необходимо писать так, чтобы программа транслировалась любым независимым компилятором (или хотя бы несколькими). Но сказать намного проще, чем сделать! Чистые компиляторы сейчас не в моде. Молодое племя программистов с трудом отличает язык от IDE, прочно подсаживаясь на иглу «Мастеров» и прочих растительных заграничных штучек. Можно долго спорить, что больше в Мастерах: пользы или вреда. Несомненно одно: человек, привыкший к Microsoft Visual C++ (вернее, к ее Microsoft C/C++ Optimizing Compiler, cl.exe - прим. AvaLANche), переходит на «правильные» трансляторы типа GCC с большим трудом, если переходит вообще, хотя, казалось бы, и то, и другое - компиляторы одного и того же языка С++.
чистый компилятор
в стандартном языке неинтересен так же, как неинтересен «чистый» язык, поскольку возможности ввода-вывода (если можно так выразиться) очень ограничены, а стандартные библиотеки совершенно непригодны для создания программ с графическим интерфейсом. Механизмы взаимодействия с операционной системой отсутствуют как класс, и даже такую простую операцию, как выдвижение CD-ROM-каретки, невозможно осуществить стандартными средствами! Вот и приходится использовать готовые компоненты, заточенные под конкретный компилятор, и нестандартные языковые расширения, которые привязывают программиста к поставщику. К тому же, хотя и существует множество открытых библиотек, которые написаны на стандартном С++ и позволяют создавать переносимые графические приложения, компилируемые любым компилятором, все-таки... Как же они тормозят! Взять хотя бы «Горящего Лиса» и сравнить его с Opera...
Решение проблемы в общем виде практически всегда проигрывает частному случаю. Это инженерный закон. Нестандартные языковые средства ускоряют процесс разработки в несколько раз, поэтому только глупые и невежественные откажутся от них там, где переносимость не требуется. Подавляющее большинство программистов руководствуется совсем не стандартом (о существовании которого многие из них даже и не догадываются), а документацией на конкретный компилятор или даже популярными книжками из серии «Microsoft Visual C++ для полных дебилов», что, в общем, правильно.
вопреки распространенному заблуждению
cтандарт пишется не для программистов, а для разработчиков компиляторов. Изучать С по стандарту ни в коем случае нельзя, и не потому, что он написан заумным языком, в котором путаются даже профессионалы. Даже не из-за обилия фраз «неопределенно, зависит от конкретной реализации». Камень преткновения в том, что ни один из компиляторов не поддерживает стандарт на 100%! Реально можно использовать только базовые языковые средства, составляющие ядро С++, а все остальное - это сплошное «шаг влево, и компилятор разваливает программу без предупреждения».
Проходит много лет, прежде чем возможности, принятые новой версией стандарта, приобретают реальную поддержку среди компиляторов. Трансляция программы - очень сложная, можно сказать, магическая штука, намного более сложная, чем кажется людям со стороны. Некоторое представление о глубине проблемы дает статья «Редкая профессия» Евгения Зуева (www.pcmag.ru/archive/9705s/05s979.asp): как три российских программиста разрабатывали С++-компилятор и с какими граблями они воевали.
Пожалей компилятор! Не насилуй его навороченными конструкциями, почерпнутыми из новейшей редакции стандарта и до сих пор еще не отработанными. Так ты существенно ограничиваешь круг компиляторов, пережевывающих твою программу, и создаешь все условия для появления трудноуловимых ошибок, генерируемых самим компилятором. Правило номер один гласит: «Не греши на компилятор и прежде всего ищи ошибку у себя». Однако из этого вовсе не следует, что компилятор никогда не ошибается. Компиляторы содержат поистине гигантское количество ошибок, и bug lists обычно имеют очень и очень внушительные объемы.
процесс программирования
на С++ нередко сравнивают с хождением по минному полю. Виновата чрезмерная сложность языка, который с возрастом становится все сложнее и сложнее. Поговаривают даже о скорой кончине С++. Симптомы загнивания и деградации налицо, пусть даже слухи о его смерти сильно преувеличены. С++ не решил тех проблем, ликвидации которых ожидали. Программирование не стало ни проще, ни эффективнее, количество ошибок ничуть не уменьшилось, удачные примеры повторного использования кода (о котором трубят все поклонники С++) можно пересчитать по пальцам одной руки… Притом совокупная себестоимость программирования существенно возросла - достаточно взглянуть на зарплату С++-программистов, цену на средства разработки и затраты на процесс обучения и освоения языка.
Программисты, одинакового хорошо владеющие двумя языками (С и С++), неоднократно замечали, что для 99% проектов 99% возможностей С++ просто не нужны! Взять хотя бы классический пример. Начинающие программисты убеждены, что форма записи «a = b + c» лучше, элегантнее и выразительнее, чем «a = add(b,c)». Однако это всего лишь заблуждение (по молодости и не такое случается). Первая запись скрывает логику программы, делая алгоритм неочевидным и заставляя программиста постоянно вспоминать, был или не был перегружен оператор сложения, какие побочные эффекты он имеет, как реализован и т.д. Стоп! Тут кто-то неожиданного говорит: «Необходимо программировать так, чтобы не было побочных эффектов». И как же можно запрограммировать?! Даже если нужно «сложить» всего две строки, дело уже не обходится без побочных эффектов, приходится выделять память, что реализуется разными путями, которые должны быть описаны в документации на перегруженный оператор сложения. Следовательно, удобство чисто внешнее, плюс в ряде случаев намного полезнее функция, которая не выделяет память, а берет ее из первой строки, что делает реализацию оператора «+» либо невозможной, либо нелогичной.
Это совсем не призыв к отказу от плюсов, а призыв к осмотрительности, осмысленности и осторожности. Программирование - это инженерная дисциплина, а всякий инженер должен руководствоваться принципом целесообразности. Вот только один пример из личной жизни. Мы с другом пишем программу.
- А давай здесь используем вот такую возможность.
- А зачем?
- Она сократит программу на пять строк и сделает ее более «наглядной».
- А ты уверен, что другие компиляторы ее поддерживают? По мне, так лучше написать пять лишних строк сейчас и никогда потом не возвращаться к этому коду, чем править твой «элегантный» код при всяком переносе на другой компилятор, мучительно вспоминая, как он работает.
Замечено, что код от программистов, не знакомых со стандартом, часто бывает более переносимым, чем код от тех, кто излазил его вдоль и поперек, горя желанием применить полученные знания на практике. Тем не менее без карты минного поля далеко не уйти, а без знания стандарта - ничего не запрограммировать. Знание пунктов стандарта и номеров статей уголовного кодекса придает программисту шарм профессиональной солидности, значительно упрощает трудоустройство, особенно на руководящие должности. Так что близкое знакомство со стандартом обещает быть отнюдь не бесполезным.
стандартизацией языка с++
занимается множество различных «инициативных» групп, основной «костяк» которых составляют ISO, IEC, JTC1, SC22 и WG21. Вместе они образуют единый комитет, который так и называется - «ISO/IEC/JTC1/SC22/WG21 The C++ Standards Committee». Его формальная глава - ISO, псевдообщественная организация, которая продвигает коммерческие решения своих создателей в качестве международных стандартов.
www.open-std.org/jtc1/sc22/wg21/ - главная страница комитета. Здесь можно подписаться на рассылку, узнать новости, поживиться различными сопроводительными материалами, но текста самого стандарта нет - он распространяется только на платой основе в печатном виде, причем бумага делается отнюдь не из конопли, а из деревьев. Бесплатно можно заточить только черновую версию, так называемый draft: www.open-std.org/jtc1/sc22/wg21/docs/papers/2001/n1316/body.pdf. Для «простых смертных» программистов, не озабоченных сертификацией своего компилятора, она вполне пригодна.
Как вариант, можно воспользоваться европейским ECMA-стандартом на язык C++/CLI, где практически слово в слово копируется полная версия стандарта на С++ (поэтому стандарт нарвался на множество упреков и нападок). Стандарт за номером 372 (www.ecma-international.org/publications/standards/Ecma-372.htm) - наш. В отличие от буржуазной ISO, Европа раздает полные версии стандартов с одной хапки, не требуя за это ни денег, ни даже традиционной регистрации. Однако, пользуясь ECMA-372, необходимо быть готовым к любых неожиданностям и несовпадениям с ISO/IEC С++ (поэтому дальше по тексту упоминается только ISO/IEC, а ECMA используется как заначка).
Согласно ISO/IEC, стандарт, которым описывается новый С, получил номер 14882, за которым идет год, в котором был принят этот стандарт. В настоящее время самой ходовой версией является стандарт от 1998 года, обозначаемый как ISO/IEC 14882:1998 (далее по тексту «старый стандарт»). Последняя редакция принята в 2003 году (новый стандарт), она реально поддерживается только несколькими компиляторами, и то криво. Следующий стандарт выйдет примерно в 2007-2010 году и будет содержать кучу нововведений.
Списка изменений комитет не ведет и тем самым вынуждает нас сравнивать различные версии стандарта самостоятельно, вычитывая и сверяя порядка 750-ти листов на английском. Правда, в Сети можно найти неофициальный перечень изменений, подготовленный третьими лицами: www.acceleratedcpp.com/authors/koenig/c++std/revisions.pdf. Здесь обнаруживается целых 300 страниц изменений, большую часть которых составляет чисто «редакторская» правка, устраняющая разночтение в формулировках. Ковыряться в этой навозной куче - неинтересное и неблагодарное занятие. По существу, что нового появилось в стандарте? Берешь какой-нибудь компилятор, наиболее полно поддерживающий новый стандарт (например EDG C++ Front End), и читаешь What's new (можно слить с www.edg.com/cpp_ftrs.html).
изменения в основном касаются шаблонов, причем многие из них носят «отвоевательный» характер. Большой победой стало утверждение экспортируемых (export) шаблонов. Формально эта возможность присутствовала еще в старом стандарте, однако мало кем поддерживалась из-за невостребованности и технических сложностей реализации. Примеры, приведенные в учебниках по С++, не транслировались ни GCC, ни Microsoft С/C++ Comiler. Долгое время их переваривал только уже упомянутый компилятор EDG, на котором, кстати, основан популярный Borland C++ Builder и малоизвестный Comeau C++.
Секретарь комитета по стандартизации Эрб Саттер (Herb Sutter) выступил с предложением убрать экспортируемые шаблоны из нового стандарта (смотри дискуссию под лозунгом «Why We Can't Afford Export» - http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1459.html). Однако предложение не прошло: восемь участников проголосовали за удаление export'а, а 28 были за то, чтобы оставить его. В итоге export оставили, что заставило разработчиков компиляторов сильно нервничать. Поддержка экспортируемых шаблонов требует значительных переделок не только компилятора, но и линкера. Многие куски кода вообще придется переписывать заново… Не поддерживать export нельзя - перестанут уважать. Полнота поддержки стандарта стала вполне весомым критерием при выборе компилятора.
Другим немаловажным достижением можно считать проработку шаблонов с частичной специализацией (partial specialisation), которые в старом стандарте описывались весьма туманно и никем, кроме EDG, не поддерживались, а зря. Как известно, шаблоны представляют собой механизм абстрактной работы с данными, «переваривающий» целочисленные переменные наряду с векторными и любыми другими типами. Удобно, но непроизводительно, поэтому для достижения максимума производительности необходимо создать специализированный шаблон (specialized template), обрабатывающий «свой» тип данных. Таким образом, у нас будет уже два шаблона: общий (general/genetic template), обрабатывающий все типы данных, и специализированный, обрабатывающий какой-то один конкретный класс с высшей эффективностью. Вот она - специализация, которая худо-бедно поддерживается большинством компиляторов еще со времен старого стандарта.
Теперь возьмем шаблон класса с несколькими параметрами. Полная специализация требует специфицировать либо все параметры шаблона, либо ни одного, что есть зло. При частичной же специализации можно специфицировать любое подмножество параметров, а остальные обрабатывать в общем виде. Дела обстоят так только по стандарту, в реальной же жизни большинство компиляторов либо совсем не поддерживают частичную специализацию, выдавая ошибку трансляции, либо молчаливо игнорируют ее, постепенно добивая программиста возле напрочь убитой программы, которая работает совсем не так, как задумывалось. Подробнее можно почитать в статье «Partial template specialisation» (www.absoluteastronomy.com/reference/partial_template_specialisation).
В целом, ситуация с шаблонами выглядит как откат к старым парадигмам и знаменует приближающийся провал. Большинство задач, решаемых шаблонами, легко решаются в рамках классического процедурного программирования и ассемблерных макросов. Если бы не убогость «сишного» препроцессора, шаблоны могли бы и не возникнуть - в них просто не было бы потребности. Специализированные шаблоны - это возврат к прежним способам обработки данных (своему типу - свой метод), но на «качественно новом уровне», который можно проиллюстрировать так. У нас было сверло отдельно по дереву и отдельно по металлу. Мы решили, что два сверла, дублирующие друг друга, - это не только невыгодно экономически, но и совсем не элегантно. Вот и изобрели универсальное сверло, берущее и дерево, и металл. Быстро выяснилось, что сверло режет погано, так как каждый материал имеет свои особенности обработки. Вместо того чтобы честно признать свое поражение, мы усовершенствовали сверлильный механизм - пусть самостоятельно распознает тип материала и автоматически изменяет геометрию профиля сверла. Конструкторы крякнули и послали всю эту раджу в небытие, даже не пытаясь ее реализовать... Ничего не напоминает?
возможности метапрограммирования
в новом стандарте также усилены. Опять-таки, мы столкнулись с возвратом к древнему самомодифицирующемуся коду, только под новым углом. В отличие от функционального программирования, метапрограммирование ориентировано на создание программ, манипулирующих другими программами или самими собой, что на С++ реализуется опять же посредством шаблонов. В частности, пример метареализации факториала выглядит так:
template struct Factorial; template <> struct Factorial<1>;
Красиво, конечно, но, увы, неэффективно!
шаблоны
представляют одну из тех областей языка, агрессивного использования которых по возможности следует избегать, поскольку качество реализации компиляторов оставляет желать лучшего. Баги в основном сосредоточены именно здесь. С другой стороны, шаблоны значительно упрощают программирование, ускоряя процесс разработки программы в несколько раз, а ошибки трансляторов исправляются по мере обнаружения. Нельзя просто сидеть и ждать. Разработчиков компиляторов нужно именно пинать, чтобы они довели поддержку шаблонов до ума.
В стороне от шаблонов идет возня по стандартизации механизма обработки структурных исключений и «декорации» (decoration) имен (также называемое «мангляжом»). До тех пор пока это не будет сделано, объектные файлы, сгенерированные различными компиляторами, останутся несовместимыми между собой и будут препятствовать созданию «смешанных» проектов. Впрочем, уже сейчас существуют линкеры, поддерживающие несколько компиляторов, например, в Microsoft Visual C++ и Borland Builder.
остальные «инновации»
в новом стандарте носят сугубо «косметический» характер, к которому относится появление типа long long или возможность записи «list<vector<string>>» вместо «list<vector<string> >» (всегдя пользовался первым вариантом - cl не возражал - прим. AvaLANche).
Компилятор GCC начиная с версии 4.0.2 также поддерживает новый стандарт, однако не в полной мере. В архиве с исходными кодами находится директория gcc/testsuite/g++.dg/tc1 - здесь тестовые примеры и текущий статус. Проваленные тесты отмечаются ключевым словом «xfail» в комментариях, что означает «данный тест еще не реализован». Ко всем остальным прилагаются «дефектные рапорты» (defect report), по одному рапорту на файл.
Компилятор в Microsoft Visual C++ 8/2005, известной под кодовым именем Whidbey (в США в штате Вашингтон есть такой остров), также поддерживает новый стандарт, но… в очень незначительной мере. Основные усилия группы разработчиков направлены в сторону выдвижения C++/CLI и на устранение ранее обнаруженных ошибок предыдущих версий. А ошибок там… В общем, Microsoft наделала просто тьму ошибок. Впрочем, дела других производителей обстоят ненамного лучше, и чтобы написать портабельную программу, компилируемую более чем одним компилятором, необходимо ограничиться лишь базовыми языковыми функциями, да и то с кучей предосторожностей.
На сайте Mozill'ы лежит руководство по созданию переносимого кода, перечисляющее основные «разногласия» приплюснутых компиляторов. Оно так и называется - «C++ portability guide» (www.mozilla.org/hacking/portable-cpp.html). Полчаса увлекательного чтения в комплекте с отборным матом и истерикой гарантированы. Правда, не всему написанному можно верить. Несмотря на то, что последняя доступная на данный момент версия 0.8 датируется 2001 годом, ситуация вовсе не так плачевна и многие из упомянутых ошибок давно исправлены. Тем не менее при переносе программы на другие платформы далеко не всегда удается найти свежий компилятор, поэтому осторожность и осмотрительность не помешают.
с++ эволюционирует
хотим мы этого или нет, он развивается от плохого к еще более плохому. Впрочем, на этот счет имеются различные мнения. Некоторые хотят видеть язык предельно простым, каким был и остается классический С. Другим требуется навороченный монстр, которого сможет осилить до степени совершенства только эксперт. Какой из этих путей «правильный»? Обратимся к естественным языкам типа русского и английского.
Язык аристократов - это сплошное нагромождение условностей и противоестественных сложностей. В нем преобладают длинные слова, сложные грамматические правила и т.д. Язык трущоб обычно бывает намного более выразительным и в то же время незамысловатым, а просочившиеся в него аристократические слова со временем теряют все лишнее и усекаются, сокращаясь по длине в несколько раз. Естественно, аристократам это не нравится: главный признак образованности, с их точки зрения, заключен в языке, точнее, в умении «владеть» им. Но что стоит за этой «образованностью», кроме знания дутых конструкций?
Или вот музыка. Сначала было бум-бум, потом - во времена Баха и Моцарта - целая симфония чувств. Эволюция? А вот и нет! С приходом попа все вернулась к прежнему бум-бум. Народ устал от сложной музыки, захотелось простых мотивов, которые тоже не стоят на месте, а с каждым годом усложняются прямо на наших глазах. И в 21 век мы въезжаем с оркестровым пением и группами типа Sirenia и Penumbra. Надолго ли?
Точно так же в программировании. Первые машинные языки были очень простыми, но они все усложнялись и усложнялись до тех пор, пока не появился С, который был воспринят многими как «студенческая подделка», «варварский откат назад» и все в том же духе...
Вполне логично ожидать, что на смену С придет язык с предельно простым синтаксисом, который можно будет выучить буквально за ночь! Не спеши тратить время на углубленное изучение тонкостей С++, возможно, они исчезнут прежде, чем успеют пригодиться...