Считанные часы остались до Нового 2014-го года, в котором в числе прочего всем нам был обещан новый стандарт C++14. Однако он будет не большим самостоятельным обновлением, а лишь доработкой C++11, багфиксом, который придаст текущей версии языка завершенный вид. На этом фоне Уильям Вонг (англ. William Wong) от ресурса electronicdesign.com взял интервью у Бьерна Страуструпа (дат. Bjarne Stroustrup), создателя C++. Беседа затронула несколько тем: от истории разработки C++ и особенностей стандарта C++11 до проблемы обучения этому языку программирования.
Некоторые термины и понятия мне раньше встречались исключительно в английском варианте (например, словечко embedded в контексте IT), и мне не всегда удавалось найти общепринятый перевод, в котором я не был бы уверен сам. В таких и других неоднозначных случаях я указывал английский вариант термина в скобках либо вовсе оставлял его непереведенным.
В конце статьи приведены ссылки и мои примечания. Владельцы указанного ресурса любезно разрешили мне перевести это интервью и опубликовать перевод на Хабре, однако настояли, чтобы я указал первоисточник определенным способом. Не обессудьте. Сразу после ссылки идет собственно перевод.
Translated from an article produced by Electronic Design, October 29, 2013, electronicdesign.com/dev-tools/interview-bjarne-stroustrup-discusses-c
На сегодняшний день языки программирования C и C++ являются самыми востребованными в области встраиваемых (embedded) систем, а также в задачах построения платформ для сторонних приложений. Автором C++ является Бьерн Страуструп. Он и поныне принимает активное участие в разработке стандартов этого языка программирования, в том числе последнего — C++11. Многое об этом языке он писал в своих книгах, например, в “Programming: Principles and Practice using C++” . Бьерн Страуструп любезно согласился ответить на несколько вопросов о самом языке C++ и о его разработке.
Как вы пришли к разработке C++?
Я работал над проектом, который позволил бы разделить ядро Unix на несколько частей, исполняемых мультипроцессором или высокопроизводительной локальной сетью. И мне потребовался инструмент, который позволил бы работать с аппаратной частью, обеспечивал бы хорошую производительность для задач системного программирования, а также мог бы использоваться для работы над системой со сложной архитектурой. Однако на тот момент (1979-1980 гг.) ни один из существующих языков программирования не удовлетворял всем трем условиям сразу. Поэтому я решил добавить к C концепцию классов — наподобие той, что была в Simula. Вначале я реализовал проверки и преобразования аргументов функций (впоследствии они стали прототипами функций), конструкторы, деструкторы, а также простейшее наследование. Первая версия языка C++ называлась «C with Classes». Кстати, любопытно, что для простейшей поддержки обобщенного программирования (generic programming) в ней использовались макросы. В дальнейшем я понял, что такой подход не обеспечивает должной масштабируемости, и вместо макросов добавил шаблоны.
Я совершенствовал архитектуру и реализацию языка C++ следующие несколько лет, вплоть до его коммерческого релиза в 1985 году. В то время производительность и скорость обращения к аппаратной части были очень важными характеристиками, впрочем, как и сегодня. Я счел необходимым реализовать в C++ все возможности языка C, причем сделать их не менее эффективными. Например, на ранних этапах я обнаружил, что структуры, используемые для реализации конструктора копирования, занимали на 3% больше памяти, чем в C. Я решил, что так быть не должно, и к концу недели все исправил. Чтобы программистам не пришлось отказываться от классов без какой-либо потери процессорного времени, были также добавлены встраиваемые (inline) функции. Я вообще был уверен в том, что используемые средства должны быть не только выразительными, но и достаточно эффективными, чтобы их можно было использовать в приложениях с самыми высокими требованиями.
К чему вы стремились при разработке языка C++?
К тому, чтобы C++ мог работать с аппаратной частью как минимум настолько же эффективно, как это делал C. Кроме того, мне показалась весьма важной концепция абстракций, которая позволила бы программистам выражать свои самые смелые идеи без каких-либо временных затрат или утечек памяти, с которыми они скорее всего столкнулись бы в самостоятельной реализации.
Это требование влечет за собой использование строгой статической типизации, в отличие от слабой типизации в C.
Язык C++ разрабатывался для людей, занимающихся программированием всерьез, то есть, для профессионалов своего дела. Он может использоваться — и используется — новичками, но часто это приводит к разным недоразумениям и жалобам на то, что не каждому дано научиться программировать на C++ и что есть вещи, которые очень сложно реализовать на этом языке. Разумеется, не существует универсального языка программирования для всего и вся, C++ и не создавался таким. Однако этот язык весьма эффективен в тех областях, для которых он был разработан, как то системное программирование или программирование программ с серьезными ограничениями на ресурсы компьютера. C++ нет равных там, где его мощь действительно нужна, и меня не сильно заботит, что вместо этого можно написать простенькое веб-приложение на JavaScript или Ruby. C++ по своей сути не предназначен для решения задач средней сложности, с нестрогими требованиями к производительности и надежности программы, равно как он не предназначен для использования не очень опытными программистами со средненькими навыками разработки. Безусловно, он может использоваться в таких условиях и сегодня это широко практикуется, но существует множество других языков программирования, которые подошли бы намного лучше.
О ключевых принципах, которых я придерживался при разработке C++, я рассказал в своей книге«The Design and Implementation of C++» и в двух статьях, написанных для конференции «History Of Programming Languages». Если вкратце, то я ставил такие цели:
- эффективная поддержка абстракции данных. Код, использующий абстракции, не должен допускать никаких накладных расходов по сравнению кодом без ее использования,
- принципы взаимодействия языка C++ и его компилятора с компьютером должны быть максимально похожими на те, что были у C,
- существенная гибкость кода, которую можно достичь при помощи абстракций, а также
- надежность кода, которая достигается при помощи строгой статической типизации.
Если в общем, C++ призван помочь в написании качественного программного кода. Профессиональные программисты в реальной жизни сталкиваются со сложными задачами, и этот язык программирования во многом упрощает их жизнь.
Хотя, конечно, не получится достигнуть все эти цели сразу, и C++ не идеален. Однако, несмотря на бесчисленные попытки создать язык вместо него, C++ со своим строгим дизайном остается лучшим решением для самых разных практических задач.
Вы принимали участие в разработке стандарта языка C++ с самого начала. Сильно ли он изменился со временем? Кто занимается разработкой новых стандартов?
Сложно сказать. Разработка формального стандарта — это очень непростое и, как правило, муторное дело. Им занимаются люди с большим опытом, однако все они — специалисты по совершенно разным областям программирования, и каждый имеет свое видение стандарта. Так что прийти к единому мнению может быть сложно и накладно по времени, но это необходимо: удовлетворить всем требованиям сразу не получится, а принуждать программистов пользоваться принципиально новыми инструментами нельзя. Прогресс происходит только когда в стандарт включаются дополнения, важность которых общепризнана. Нельзя принимать участие в комитете и при этом постоянно зацикливаться на мелочах. Нужно уметь видеть всю картину в целом и приходить к общему мнению с остальными. По моим подсчетам, в комитет входит около сотни организаций и, может, больше трехсот собственно разработчиков. Это в два-три раза больше, чем было раньше. Только на последних собраниях присутствовало около ста человек.
В 2014 году мы планируем выпустить новый стандарт, C++14. В нем будут минимальные нововведения, а также несколько исправлений, за их необходимость уже проголосовало большинство комитета. Я рассчитываю, что в 2014 году все уже будут использовать C++14, а после этого мы планируем выпустить C++17 в 2017 году. Но это обновление будет уже намного существенней, так что тут сложно судить о сроках.
Комитет по разработке стандартов ISO C++ сам по себе не располагает какими-либо ресурсами, будь то деньги или штатные разработчики. Он полностью основан на средствах его участников. Например, чтобы входить в состав комитета, они платят 1200 долларов ежегодно. Всякий может заявить, что, мол, в C++ нет нормальной библиотеки для создания графических интерфейсов или нормальной поддержки параллелизма задач. Да, мы в курсе. Чем жаловаться, лучше бы помогли довести эти задачи до ума. У нас очень мало прикладных программистов, и часто получается так, что нововведения создаются в угоду интересов одного конкретного разработчика.
Многие программисты при разработке встроенных систем предпочитают использовать C, потому что он проще, чем C++, и больше подходит для разработки под аппаратное обеспечение. Действительно ли сложность C++ должна быть камнем преткновения для разработки встроенных систем?
Вовсе нет. Если вы придерживаетесь C-стиля программирования, то C++ окажется ничуть не сложнее C, причем он тоже подходит для разработки под аппаратное обеспечение. И уж точно C++ намного эффективнее, чем C. Я никогда не видел такой программы на C++, которую можно было бы так переписать на C, что у нее будет меньший объем кода, она будет производительней, она будет лучше сопровождаться — в общем, будет эффективнее. Не верю, что такое возможно.
Миф о том, что «C лучше C++», сбивает с толку очень многих начинающих программистов. Так, например, когда они сталкиваются с проблемами, они постоянно пытаются что-то выдумывать и применять совершенно нетривиальные вещи, а не использовать простые и мощные инструменты. В конце концов, у них получается очень сложный и запутанный код, который они в силу своих заблуждений принимают за эталон. Вся эта ситуация меня просто поражает. Если человек берется за что-то, а ему постоянно твердят, что это очень сложно и бесполезно, то у него в итоге ничего и не получится. Единственная вразумительная причина, из-за которой, как я знаю, используют чистый C, а не C++, — это ограниченные возможности конкретной платформы.
Однако студентов и вообще новичков в изучении C++ нельзя винить, потому что их ошибки часто зарождаются в процессе освоения университетского курса программирования. Однажды, лет десять назад, мне довелось вести его у первокурсников. Я заглянул в учебники — и просто поразился: вместо понятных и простых в использовании конструкций C++ в книгах в начале рассматривалась куча разных неочевидных мелочей языка C, а инструменты С++ преподносились как нечто очень сложное. Это не отпугивало только тех, кто хотел серьезно заниматься программированием.
Вот серьезно, скажите: неужели вектора из стандартной библиотеки сложнее массивов из C? Или, например, почему студентов приучают к функции qsort()
, хотя sort()
и эффективнее, и универсальнее? У C++ более строгая типизация, чем у C, за счет этого объектный код обрабатывается быстрее.
Еще в учебниках часто описывают C++ как провалившуюся попытку создания чистого объектно-ориентированного языка программирования. Такое утверждение как правило иллюстрируется целой простыней кода, в которой практически вся архитектура разбита на запутанную иерархию классов, унаследованных друг от друга. В итоге получается совершенно не характерная для C++ связанность. Такой код напоминает скорее программу на Java, и, что самое печальное, работает он обычно медленно.
Мне тоже не нравится C++ таким, каким его представляют авторы тех учебников. В ответ я написал свою книгу для студентов и самоучек — «Programming: Principles and Practice using C++». Для ее изучения опыт программирования не обязателен, однако она вызвала интерес и среди опытных разработчиков.
Только если вам нужен просто обзор C++11, то эта книга будет довольно большой. Для этой цели я порекомендовал бы книгу «A Tour of C++». В ней описаны все ключевые моменты ISO C++ и стандартной библиотеки всего на 180 страницах. Стандарт C++11 полностью поддерживается компиляторами Clang и GCC, частично — Microsoft C++ и многими другими, правда, боюсь, на менее популярных платформах он может выполняться некорректно.
В C++ 11 было много нововведений, в том числе лямбда-выражения и поддержка многопоточного программирования. Как вы считаете, оказались ли они востребованными?
Для работы с потоками мне постоянно приходилось пользоваться сторонними библиотеками. Они были хороши, но последние пятнадцать лет я хотел добавить поддержку потоков именно в стандарт, чего мы наконец и достигли. С точки зрения параллельного программирования ключевые новшества C++11 состоят в организации памяти (которую, к слову, заимствовали и для языка C), а также портируемости многопоточных программ. Однако если вы программируете на уровне потоков и барьеров, то самым главным для вас может оказаться безопасное преобразование типов (type safety): для разделения и передачи данных между потоками больше не требуется никаких макросов или void**
. Впрочем, для кого-то также важны инструменты и для lock-free программирования.
Что касается лямбда-выражений, то я примерно лет десять работал над такой их реализацией в языке C++, от которой было бы больше пользы, чем вреда. У сторонних библиотек как правило страдает производительность. Нам же удалось добиться для лямбда-выражений производительности, сравнимой с циклом for
. Приведу пример:
double sum = 0;
for (int i=0; i<v.size(); ++i) sum += v[i]; // array style
double sum = 0;
for (auto p = v.begin(); p!=v.end(); ++p) sum += *p; // pointer style
double sum = 0;
for_each(v.begin(),v.end(), [&](double d) { sum += d; }); // algorithm style
Эти три участка кода делают одно и то же и работают с одинаковой производительностью. Между ними можно выбирать исходя из соображений стиля программирования, поддерживаемости и так далее.
Я бы применял лямбда-выражения всего в нескольких случаях. В таком, например:
sort(v, [](const string& a, const string& b) { return a>b; }); // sort in reverse order
Лямбда-выражения — новый и довольно мощный инструмент. Подвох в том, что такие вещи программисты любят использовать буквально повсюду, пока не понимают, чем это обернется в дальнейшем. Мне, например, кажется, что функцию и функциональные объекты лучше объявлять отдельно. У нужной операции тогда будет свое собственное имя, которое можно легко вызвать из разных участков программы.
Также лямбда-выражения открывают большой простор для практики написания кода. Здесь, наверное, не место учить вас C++11, но позвольте мне привести один пример:
template<typename C, typename V>
vector<Value_type<C>*> find_all(C& cont, V v) // find all occurrences of v in cont
{
vector<Value_type<C>*> res;
for (auto& x : cont)
if (x==v)
res.push_back(&x);
return res;
}
В этом коде я применил несколько новых для C++ вещей. Цикл for
, например, здесь читается как «для всех x
из cont
» и упрощает перебор контейнера cont
. Объявление auto& x
показывает, что x
должна быть ссылкой на тип элементов инициализирующего контейнера, в данном случае — на тип элементов cont
. Данный цикл собирает адреса всех вхождений v
в cont
и складывает их в вектор указателей res
. Так что эти конструкции — не больше чем синтаксический сахар, хотя они весьма удобны.
Существенное же нововведение заключено в return
: обратите внимание, что я по вернул вектор по значению. В C++98 этот оператор возврата создал бы копию res
, а ведь на деле он может оказаться большим и состоять из тысяч элементов. С точки зрения производительности это было бы весьма опрометчиво. А в C++11 у векторов есть так называемый конструктор перемещения (move constructor), который вместо копирования «заимствует» представление res (в сущности, всего три указателя) для использования на месте вызова функции find_all()
, а сам вектор оставляет пустым. После выполнения return
мы больше никогда не сможем использовать res
. Таким образом, возврат вектора по значению обойдется максимум в шесть присваиваний, вне зависимости от размера вектора.
Конструкторы перемещения — довольно простой инструмент. Он доступен каждому программисту, и, кроме того, реализован в каждом контейнерном классе стандартной библиотеки. Это позволяет без труда возвращать из функций большие объекты и не ломать лишний раз голову над управлением памятью.
Протестировать функцию find_all()
можно следующим образом:
void test()
{
string m {"Mary had a little lamb"};
for (const auto p : find_all(m,'a')) // p is a char*
if (*p!='a')
cerr << "string bug!n";
vector<string> v {"Mary”, “lamb", “Mary”, “mary”, “wolf”};
for (const auto p : find_all(v,”Mary”)) // p is a string*
if (*p!=”Mary”)
cerr << "vector<string> bug!n";
}
Попробуйте написать тот же код без применения шаблонов и нововведений C++11 и сравните результаты.
На эту тему рекомендую прочесть «A Tour of C++», за деталями же обратитесь к четвертому изданию книги «The C++ Programming Language».
Какие часто встречаемые у современных C++-программистов ошибки вы можете отметить?
Они почему-то часто думают, что должны выбирать между эффективным и изящным кодом. Они либо ограничивают себя низкоуровневыми возможностями языка (ради «эффективности»), либо выстраивают огромную архитектуру «на все случаи жизни» (полагая такой код весьма элегантным). На мой же взгляд, идеал состоит в сочетании максимальной эффективности и наиболее оптимальной архитектуры. Так происходит, когда решение программиста самым оптимальным образом удовлетворяет условиям задачи, но это, разумеется, не всегда возможно. Редко получается добиться этого с первой попытки, но так случается довольно часто, насколько это возможно для идеала.
Прежде чем отказываться от возможностей C++, вроде классов или шаблонов, попробуйте сперва рассмотреть их базовое применение и попрактиковаться. Обоснованный выбор приведет вас намного дальше, чем шаги, предпринятые наугад. Не обязывайте себя сооружать огромные иерархии классов или писать запутанный мета-код с помощью шаблонов: некоторые из самых мощных возможностей C++ очень просты. Лучший путь к написанию эффективного кода — не усложнять его без необходимости.
Чем вы любите заниматься в свободное время?
Мне интересно путешествовать по разным местам. Еще я люблю выходить на пробежки. Также я увлекаюсь фотографией, мне нравится слушать музыку и читать — художественную и историческую литературу. Время стараюсь проводить с родными и близкими. Само собой, программирование мне тоже порой доставляет немало удовольствия, но вопрос, должно быть, не о работе. Мне нравится заниматься исследованиями, собирать сложные программные системы. Как кто-то сказал, «не могу поверить, что мне за это еще и платят!»
Примечания переводчика
- «Программирование. Принципы и практика использования C++» в переводе Дмитрия Клюшина, издательство «Вильямс». назад
- О типизации см. статью Ликбез по типизации в языках программирования. назад
- Вероятно, здесь все-таки имеется в виду книга «The Design and Evolution of C++» — «Дизайн и эволюция C++» в переводе издательства «Питер». назад
- Модель памяти C++11 подробно рассмотрена в статье Lock-free структуры данных. Основы: Модель памяти. назад
- Краткий обзор move semantics в C++11 приведен в статье Move semantics в C++11 и STL-контейнеры. назад
- В целом ключевые нововвдения C++11 (в том числе и вышеупомянутые) освещены в статье Десять возможностей C++11, которые должен использовать каждый C++ разработчик.
- В русском переводе Николая Мартынова, издательство «Бином», книга называется «Язык программирования C++», однако я смог отыскать только перевыпуск бородатого, неактуального издания 2001-го года. Других переводов не нашел вовсе. назад
Автор: kazarey