Должно ли C++ сообщество придерживаться стандарта или отойти от него, чтобы создавать лучшие вещи с Boost?
Погодите, не та библиотека.
В марте 2011 года комитет ISO C++ утвердил финальную версию черновика новейшего стандарта C++. Языка, который официально был стандартизирован в августе того же года и стал известен как C++ 11. Теперь, по прошествии 2 лет, мы можем оглянуться назад и посмотреть на некоторые проблемы, затронувшие язык(аж с момента принятия первого международного стандарта в 1998 году) и сравнить его финальный вариант с популярной C++ библиотекой Boost.
С первой версией стандарта С++ включил официальную спецификацию библиотеки. До этого различные организации и частные лица создавали свои библиотеки, иногда основываясь на шаблонах и предоставляя различные типы контейнеров, таких как вектор или стек. Одной из таких библиотек, заслужившей внимание ANSI/ISO, была Стандартная Библиотека Шаблонов (Standard Template Library — STC), начатая парнем по имени Александр Степанов. Но на тот момент не существовало стандарта на сам язык, и различные поставщики компиляторов имели возможность трактовать его как вздумается. (Помню, как в середине 90-х была возможность «сбрасывать ссылки» с компилятором Borland C++, что сейчас невозможно с выходом стандарта).
Шаблоны были отельной проблемой, т.к. вендоры реализовывали их по-разному. Однако, появление STC заполнило важный пробел в C++, в частности, потребность в четко определённых и хорошо протестированных классах-контейнерах. Но, чтобы жизнь мёдом не казалась, вскоре появились различные реализации STC. Помню, как я использовал версию от HP(которая была одной из оригинальных), а моя компания в то же время выбрала версию от Silicon Graphics. В то же время существовала эта чуднАя библиотека от Rogue Wave Software, которая включала тонны дополнительных классов — некоторые на основе шаблонов, некоторые — нет; она была одна из самых популярных коммерческих библиотек того времени и отчасти напоминала STC. Важный момент: ни одни из этих библиотек не были взаимозаменяемыми. Нельзя было просто так взять, и заменить "#include" на другую библиотеку и ожидать, что всё будет работать, даже если предполагается, что используются те же самые классы и функции. Они просто не были совместимы. То, что 2 библиотеки реализовали класс «вектор», базируясь на оригинальной STC, не означало что он был одинаковым.
Хаос
Тогда в 1998 вышел стандарт, который включал собственную спецификацию библиотеки, названной Standard Library. Она во многом походила на STC и на самом деле базировалась на ней. Несмотря на то, что она была частью спецификации языка, Standard Library не являлось его частью в том смысле, что не была встроена в компилятор. Библиотека была спецификацией для набора классов, которые вендоры могли разрабатывать на C++ и поставлять вместе с компилятором. Это значило, что вы могли проигнорировать Standard Library если хотели, и вместо неё использовать какую-нибудь другую — что и делало большинство организаций, включая ту, в которой работал я, молодой инженер, создающий софт для телекоммуникационной индустрии. В этой компании программисты, работавшие до меня, использовали версию STL от Silicon Graphics, и потребовались огромные усилия, чтобы отойти от неё и заменить реализацией Standard Library. Конечно, в 1999 мы перешли на последние версии компиляторов и сделали множество изменений, после чего вендоры с гордостью объявили, что их компиляторы теперь соответствуют стандарту, что означало, что большая часть написанного нами кода не всегда компилировалась новыми компиляторами.
И так, разработчики ПО были вынуждены выбирать, когда начинали новые проекты: использовать Standard Library или проверенную STL, а если STL — то какую версию? Или остановиться на отличной коммерческой библиотеке вроде Rogue Wave? Они так же понимали — однажды выбрав одну из них, они будут вынуждены использовать её долгое время. Что же касается самой Standard Library, поставщики компиляторов часто создавали собственную её версию, которая не всегда с ней совпадала с оригиналом.
Это был полный бардак. И частично, проблемой был(и сейчас есть) сам процесс стандартизации. STL впервые был продемонстрировал Комитету по Стандартизации в 1993, но прошло 5 лет, прежде чем вышел стандарт. В промежутке не существовало даже стандарта на сам язык, не говоря уж про библиотеки, поэтому вендоры компиляторов получили не только свободу создавать библиотеки, но и сам язык. Примерно к 1996 году бардак достиг своего апогея. Borland включил множество новых фич в свою версию C++, но невозможно было просто скомпилировать их код другим компилятором. И если вы хотели использовать STL, вам нужно было найти ту версию, которая была портированна для вашего компилятора.
К 1998 году большинство вендоров обновили свои компиляторы в соответствии со стандартом, но это не очень помогло, т.к. было написано огромное число кода для более ранних версий. На самом деле, некоторые компании не могли перейти на новые компиляторы, потому что их старый код перестал бы компилировался. Много чего произошло за 6 лет в компьютерном мире, и поставщики компиляторов продолжали выпускать новые версии, несмотря на грядущий стандарт.
В этом процессе мало что изменилось. В 2006 году (через 3 года после небольшого обновления), казалось новый стандарт C++ должен стать реальностью. Он наконец вышел в 2011 году. Прошла куча времени.
Boost
В начале 2000-х новая библиотека набирает популярность — Boost.
Boost — это набор C++ библиотек, которые заполняют различные пробелы не только в Standard Library, но и в самом языке, двигая его вперёд. C++ вышел в 1983 году, после 4 лет разработки, и базировался на C, который вышел в 1972.
Давайте начистоту: несмотря на то, что я очень люблю C++ и даже до сих пор его использую(особенно для многоядерного программирования, которое является одним из моих фриланс-проектов), факт в том, что чем больше вы проводите времени с современными языками, такими как Java или Python, тем больше вы находите полезных фишек, встроенных непосредственно в язык — фишек, которые до сих пор не являются частью C++. Чёрт, тип string до сих пор не встроен в ядро языка, а реализован как шаблон в Standard Library. Язык сам по себе всё ещё базируется на символьных указателях и ссылках. Если вам нужен string — вы должны использовать одну из библиотек и часто звать метод класса string c_str(), чтобы получить указать на символ, потому что string не встроен нативно в язык, не говоря уже о штуках типа list или dictionary. Хотите list в Python? Просто наберите «a = [1,2,3]». Готово.
Что же касается более продвинутых вещей, вроде лямбда-функций(которые так же нативно поддерживаются многими языками) — вот где библиотеки типа Boost будут полезны.
Конечно, фундаментальное отличие C++ в том, что он полностью компилируем. Но это аргумент, с которым можно спорить до тошноты, лучше и быстрее ли С++ современных динамических языков как с JIT компиляцией, так и без неё.
Библиотека Boost в конечном счёте служит нескольким целям: во первых, она предоставляет программистам как продвинутые вещи, такие как функциональное программирование, так и базовые, вроде смарт-указателей. Во-вторых, она является своего рода инкубатором для новых возможностей языка, которые могут стать стандартными.
Прямо на главной странице Boost замечено, что 10 библиотек из её состава было включено в стандарт C++ 11. Да, и она вышла в 2003 году — 10 лет назад, задолго до принятия стандарта.
Т.к. принятие стандарта 2011 года заняло так много времени, множество разработчиков начали использовать Boost(или совсем отказались от C++). Итак, давайте сравним Boost со Стандартом.
Стандарт 2011 и Boost
Стандарт C++ 11 включает несколько изменений в самом языке(например, приведён в порядок механизм компиляции шаблонов, что позволяет использовать «внешние шаблоны» («extern template»), а как же проще инициализировать объекты-контейнеры; и даже мехнизм определения типов(type inference mechanism)). Но так же стандарт содержит множество улучшений в Standard Library.
Тут есть важная оговорка, упомянутая ранее: несмотря на то, что стандарт включает описание Standard Library, он не включает имплементацию ни компилятора, ни языка. Всё это остаётся на совести третьих лиц, таких как поставщики компиляторов. Не существует эталонной реализации. Стандарт — это не код, это описание.
Так же, одна вещь, которую нужно знать о Boost — это что она на самом деле состоит из нескольких библиотек, и её дизайнеры намеренно раздели их. Это значит, что вы можете использовать только те библиотеки из Boost, которые вам нужны. Там есть несколько взаимозависимостей, но они хорошо документированы. Таким образом, вы можете комбинировать Boost со Standard Library, если необходимо. Например, старые добрые iostream являются частью Standard Library со времён первого стандарта 1998 года, и Boost легко работает с ними(но так же включает свои собственные iostream, если вам так удобнее).
Но Boost, на самом деле, гораздо больше, чем Standard Library. Она содержит около 175 классов, и текущая версия на SourceForge(1.53.0) занимает 93 Мб (включая документацию, а распакованные .hpp файлы в количестве 9000 штук занимают 90 Мб). Там есть классы, которые лично я нахожу невероятно полезными в моей работе, например те, что относятся к категории «эмуляция особенностей языка» («Language Features Emulation»). Например — конструкция foreach, спасибо механизму шаблонов в C++, позволяет писать циклы, которые выходят за рамки стандартных циклов C++, вот так:
int i = 0;
BOOST_FOREACH( i, container )
{
…
}
Хотя вы можете легко писать код, используя нативные циклы, есть несколько причин, почему вы можете захотеть писать их таким способом. Первая — кодирование становится чуточку проще. Вторая — джуниоры(или даже сеньоры, которые, как вы думаете, никогда не должны быть повышены) с меньшей вероятностью сделают всё ту же старую ошибку, написав что-то вроде "for (i=0; i<=length;i++)", а потом ещё будут спрашивать вас, почему этот код иногда работает, а иногда падает внутри функций, из которых был вызван.
Boost так же предоставляет огромный ассортимент могучих, но узко специализированных классов. Например, несколько лет назад мне понадобилась библиотека для работы с графами. В Boost есть такая. Нужен многомерный массив? Как насчёт контейнера, основанного на стеке? Boost всё это имеет. Но что мне больше всего нравится в ней — это целый набор хороших шаблонов программирования и полезная библиотека для работы с регулярными выражениями.
Но реализация Boost не очень проста, разработчикам пришлось наполнить библиотеку компиляторозависимым кодом. Например, реализация foreach конструкции включает такой код:
# if BOOST_WORKAROUND(BOOST_MSVC, <= 1300)
|| BOOST_WORKAROUND(__BORLANDC__, < 0×593)
|| (BOOST_WORKAROUND(BOOST_INTEL_CXX_VERSION, <= 700) && defined(_MSC_VER))
|| BOOST_WORKAROUND(__SUNPRO_CC, < 0×5100)
|| BOOST_WORKAROUND(__DECCXX_VER, <= 60590042)
# define BOOST_FOREACH_NO_RVALUE_DETECTION
# endif
Это хорошо для всех, использующих библиотеку — она будет работать с большинством распространённых компиляторов и полностью абстрагирует от их тонкостей.
Но теперь есть другой стандарт
Итак, Boost продолжал расти, но и над стандартом велась работа все годы, предшествовавшие 2011-ому. Он содержит некоторое хорошие классы, такие как собственных класс регулярных выражений, который имеет много общего с Boost-им аналогом.
Но что насчёт стандарта? Посмотрим на это с точки зрения вендоров. Стандартам C++ требовались годы, чтобы стать законченными. Стандарт 1998 был важен, потому что он устанавливал множество фундаментальных аспектов языка, таких как шаблоны или ссылки. Это сделало жизнь создателей библиотек гораздо проще. Но компиляторы всё ещё отличаются друг от друга, о чём свидетельствуют компиляторозависимые макросы в Boost. Частично, это вина вендоров, которые не буквально следовали стандарту, но так же из-за случайных неопреленностей в самих библиотеках.
В течении нескольких лет, предшествовавших стандарту 2011, в него так же было внесено несколько изменений. Поставщики компиляторов пытались привести свои компиляторы и реализации Standard Library, надеясь иметь уже стандартизированные продукты до того, как стандарт будет закончен. Но они стреляли по движущейся цели.
Давайте взглянем на 2 конкретных компилятора: GNU GCC и Microsoft's C++. Согласно этой таблице, текущая версия GCC 4.8 поддерживает почти все новые фичи, за исключением двух небольших (Rvalue ссылки для "*this" и «минимальная поддержка сбора мусора»). Но разработчики ясно дали понять, что это поддержка всё ещё в экспериментальном статусе.
Эта таблица от Microsoft показывает статус в Visual Studio 2012 — там множество «no» и «partial». Но обратите внимание на эту небольшую заметку на странице Microsoft, которая даёт представление о том, с чем она имели дело:
"После утверждения лямбда-функций в Рабочем Документе (версия 0.9) и изменяемых лямбд (mutable lambdas), добавленных в версию 1.0, Комитет по Стандартизации начал переделку. Это привело к появлению лямбд версии 1.1. Это произошло слишком поздно, чтобы быть включенным в Visual C++ 2010, но они есть в Visual Studio 2012."
Внимание здесь на самом языке. Но что насчёт Standard Library? Microsoft включилась в работу над тем, чтобы соответствовать стандарту, на самых ранних стадиях. Тем не менее, они пояснили, что некоторые вещи в этой библиотеки зависят от новых фич языка, которые они ещё не реализовали. И как результат — эти аспекты библиотеки так же не были реализованы. (Но они не указали, какие именно).
А вот GNU реализация Standard Library. Там много элементов, помеченных «N» и «Partial».
Сравнение
Прошло 10 лет, прежде чем начал формироваться стандарт и ушло 2 года на его утверждение. Множество изменений произошло за это время, и производители компиляторов не могут рисковать, реализовывая те из фич, которые могут быть потом отменены. Не очень приятная картина.
Boost, в то же время, неплохо продвинулся. Разработчики проделали отличную работу, убедившись, что их код работает с различными компиляторами. Boost зрела, хорошо оттестирована и неплохо работает. Процесс разработки продвигается вперёд без каких-либо препятствий со стороны стандартизационного комитета. Она продолжает развиваться.
А что насчёт изменений в Standard Library? Я уже упоминал библиотеку регулярных выражений, очень похожую и находящуюся под сильным влиянием Boost. Но учитывая тот факт, что Standard Library всё ещё не хватает реализации многих вещей из спецификации, имеет ли это значение? Помните, C++ стандарт — это не код, и не существует эталонной реализации. Стандарт просто говорит о том, что C++ компилятор должен делать, и что должно быть в реализации Standard Library. Если вы хотите использовать Standard Library, вам нужно использовать продукт третьих лиц, часто поставляемый вместе с компилятором, таким как GCC или версия Microsoft. И вот из чего вы на самом деле выбираете: GCC или Microsoft-ая реализация Standard Library или Boost, на основе которой Standard Library была смоделирована. И обе этих имплементации не являются полной, а одна из них называется «экспериментальной». Какую вы предпочтёте для использования в своих продуктах?
Комитету по Стандартизации потребовалось 8 лет чтобы разобраться, что же всё-таки должно быть в стандарте, и производителям компиляторов пришлось бы ждать, пока всё будет улажено, прежде чем публиковать свою версию Standard Library. Вместо этого они публиковали куски, которые были в лучшем случае альфа-версиями. И они до сих пор не закончили. В то же время настоящее C++ сообщество движется дальше, создавая лучшие вещи, такие как Boost.
Через несколько лет появится новый стандарт. Мы это уже проходили. К тому времени Boost станет ещё лучше.
Автор: Captcha