Часть 1
Не у каждого объекта может быть один владелец. Нам надо убедиться, что объект уничтожен и освобождён, когда исчезает последняя ссылка на него. Таким образом, нам необходима модель разделённого владения объектом. Допустим, у нас есть синхронная очередь, sync_queue, для общения между задачами. Отправитель и получатель получают по указателю на sync_queue:
Предполагается, что task1, task2, iqueue и oqueue уже где-то были соответствующим образом определены и прошу прощения за то, что thread переживёт область видимости, где они были созданы (посредством detatch()). Вопрос: кто удалит sync_queue, созданные в startup()? Ответ: тот, кто последний будет использовать sync_queue. Это классический случай, когда требуется сборка мусора. Изначально сборка подсчитывала указатели: нужно хранить количество использований объекта, и в тот момент, когда счётчик обнуляется, удалять его. Множество современных языков работают так, а С++11 поддерживает эту идею через shared_ptr. Пример превращается в:
Теперь деструкторы task1 и task2 могут уничтожить их shared_ptr (и в большинстве правильно построенных систем так и сделают), и последнее, что нужно сделать – уничтожить sync_queue. Это просто и довольно эффективно. Никакой сложной системы. Что важно, она не просто возвращает память, связанную с sync_queue. Она возвращает объект синхронизации (мьютекс, блокировку, что угодно), встроенный в sync_queue, чтобы синхронизировать две нити, выполняющие две задачи. Это не просто управление памятью, это управление ресурсами. Этот «скрытый» объект синхронизации обрабатывается так же, как хендлы файлов и потоков в предыдущем примере. Можно попробовать избавиться от использования shared_ptr, введя уникального владельца в какой-либо области видимости, заключающей в себе задачу, но это не всегда просто сделать – поэтому в С++11 есть и unique_ptr (для одиночного владения) and shared_ptr (для разделённого владения).
Я говорил пока о сборке мусора в контексте управления ресурсами. Но есть ещё и типобезопасность. У нас есть операция delete, которую можно применить неправильно. Пример:
Не надо так делать. Непосредственное применение delete опасно и не нужно в обычных случаях. Оставьте удаления классам, управляющим ресурсами — string, ostream, thread, unique_ptr и shared_ptr. Там удаления аккуратно отслеживаются.
С моей точки зрения, сборка мусора – последнее средство для управления ресурсами, а не решение задачи и не идеал.
1. Используйте подходящие абстракции, которые рекурсивно и неявно обслуживают свои ресурсы. Отдавайте предпочтение им, а не переменным в определённой области видимости.
2. Когда вам необходимо использовать указатели и ссылки, используйте умные указатели — unique_ptr и shared_ptr
3. Если ничего не помогает (например, ваш код – часть программы, которая запуталась в указателях, и не использует стратегию, поддерживаемую языком, для управления ресурсами и обработки ошибок), попробуйте обрабатывать ресурсы, не относящиеся к памяти, вручную, и включайте сборку мусора для обработки неизбежных утечек памяти.
Многие верят в то, что эффективный код обязан быть низкоуровневым. Некоторые даже верят, что низкоуровневый код обязательно эффективен. («Если оно такое уродливое, наверняка оно быстрое! Кто-то потратил кучу времени и своего таланта для создания этой штуковины!»). Конечно, можно писать эффективный код на низком уровне, и некоторый код необходимо делать низкоуровневым для работы с машинными ресурсами. Измеряйте, однако, стоит ли это ваших усилий. Современные компиляторы С++ очень эффективные, а архитектура современных машин чрезвычайно сложна. При необходимости такой низкоуровневый код необходимо прятать за интерфейсом для удобства. Часто сокрытие низкоуровневого кода за высокоуровневым интерф��йсом способствует оптимизации. Там, где важна эффективность, сначала попробуйте достичь её, выразив идею на высоком уровне, не кидайтесь сразу на биты и указатели.
Простой пример. Если вам надо отсортировать набор чисел с плавающей точкой по убыванию, вы могли бы написать для этого код. Но если у вас нет экстремальных требований (например, чисел больше, чем может уместиться в памяти), это было бы наивно. За десятилетия мы сделали библиотеки с алгоритмами сортировки с приемлемой скоростью работы. Мне меньше всего нравится qsort() из стандартной ISO библиотеки C:
Если вы не программируете на С, или если вы в последнее время не использовали qsort, потребуется кое-что объяснить; qsort принимает 4 аргумента
— указатель на последовательность байтов
— количество элементов
— размер элемента
— функция, сравнивающая два элемента, которые передаются как указатели на их первые байты
Этот интерфейс скрывает информацию. Мы сортируем не байты – мы сортируем double, но qsort этого не знает, поэтому нам надо предоставить информацию о том, как сравнивать double, и сколько байтов в double. Конечно, компилятор знает такие вещи. Но низкоуровневый интерфейс qsort не позволяет компилятору воспользоваться этой информацией. Необходимость указывать такую простую информацию ведёт к ошибкам. Не перепутал ли я два целых аргумента qsort? Если перепутаю, компьютер этого не заметит. Соответствует ли моя compare() соглашениям в C для трёхстороннего сравнения? Если вы посмотрите на промышленную реализацию qsort (рекомендую), вы увидите, сколько усилий приложено для компенсации недостатка информации. К примеру, довольно трудно произвести смену местами элементов, заданных в виде количества байт, чтобы это было так же эффективно, как смена местами пары double. Затратные непрямые вызовы функции сравнения могут быть устранены компилятором только в том случае, если он применит распространение констант для указателей на функции.
Сравним qsort с его эквивалентом sort из С++
Здесь требуется меньше объяснений. Вектору известен его размер, и нам не надо явно указывать количество элементов. Тип элементов не теряется, и не нужно помнить об их размере. По умолчанию, sort сортирует по возрастанию, поэтому пришлось задать критерий сравнения, как и для qsort. Здесь он передан в качестве лямбда-выражения, сравнивающего два double при помощи >. И так получилось, что эта лямбда тривиальным образом инлайнится всеми компиляторами С++, что я знаю, поэтому сравнение превращается в одну машинную операцию «больше, чем» — никаких неэффективных вызовов функции.
Я использовал контейнерную версию sort, чтобы не задавать итераторы явно, то есть, чтобы не писать:
Можно пойти дальше и использовать объект сравнения С++14:
Какая из версий быстрее? Можно скомпилировать версию qsort как С, так и С++ без всякий различий в быстродействии, поэтому это будет скорее сравнением стилей программирования, а не языков. Библиотечные реализации используют один алгоритм для sort и qsort, поэтому это сравнение стилей программирования, а не алгоритмов. Конечно, у разных библиотек и компиляторов будут разные результаты, но для каждой реализации будет видна разумная реакция на разные уровни абстракции.
Я недавно прогнал примеры, и увидел, что sort в 2.5 ��аза быстрее, чем qsort. Это может меняться от компилятора к компилятору и от компьютера к компьютеру, но ни разу у меня qsort не выиграл у sort. Иногда sort выполнялся в 10 раз быстрее. Почему? В стандартной библиотеке С++ sort явно выше уровнем, чем qsort, при этом более гибкий и общий. Он типобезопасен и параметризован на типе хранения, типе элементов и критерию сортировки. Никаких указателей, размеров, байтов. Библиотека STL, к которой принадлежит sort, старается не выбрасывать никакой информации. Это приводит к превосходному инлайнингу и хорошей оптимизации.
Обобщение и высокоуровневый код могут выигрывать у низкоуровневого. Не всегда, но сравнение sort/qsort – это не единичный пример. Всегда начинайте с высокогоуровневой, точной и типобезопасной версии решения. Оптимизируйте по необходимости.
С++ — объёмный язык. Размер определений схож с С# и Java. Но это не значит, что вам нужно знать каждую деталь, чтобы использовать его, или использовать все функции непосредственно в каждой программе. Вот пример использования основных компонент из стандартной библиотеки:
Предполагаю, что вы знакомы с регулярками. Если нет – самое время ознакомиться. Заметьте, что я полагаюсь на семантику перемещений, чтобы просто и эффективно вернуть потенциально большой набор строк. Все контейнеры стандартной библиотеки обеспечивают конструкторы перемещения, поэтому нет нужды возиться с new.
Для работы примера требуется включить компоненты:
Проверим:
Просто пример. Легко можно поменять get_addresses(), чтобы она принимала регулярку как аргумент, чтобы она могла искать URL или что угодно. Легко поменять get_addresses(), чтоб она распознавала больше одного вхождения шаблона в строке. С++ предназначен для гибкости и обобщений, но не каждая программа обязана быть фреймворком. Суть в том, что задача извлечения емейлов из потока просто выражается и просто проверяется.
На любом языке писать программу только через встроенные возможности языка (if, for, и +) утомительно. И наоборот, при наличии подходящих библиотек (graphics, route planning, database) любую задачу можно выполнить, приложив разумные усилия. Стандартная ISO библиотека С++ относительно небольшая (по сравнению с коммерческими), но помимо неё есть много библиотек как с исходным кодом, так и коммерческих. К примеру, при помощи библиотек Boost, POCO, AMP, TBB, Cinder, vxWidgets, CGAL сложные вещи становятся проще. К примеру, пусть наша программка извлекает URL с веб-страницы. Для начала, мы обобщим get_addresses() для поиска любой строки, совпадающей с шаблоном.
Это упрощённая версия. Теперь надо как-то прочесть файл из веба. В Boost есть библиотека asio для работы с вебом:
Общение с веб-сервером довольно непростое:
При разборе файла www.stroustrup.com/C++.html это даёт:
www-h.eng.cam.ac.uk/help/tpl/languages/C++.html
www.accu.org
www.artima.co/cppsource
www.boost.org
…
Я использовал множество, поэтому URL выводятся по алфавиту.
Я спрятал проверку соединения в connect_to_file():
Я не писал всё с нуля. Работа с HTTP скопирована с документации по asio.
С++ — компилируемый язык, предназначающийся для создания хорошего, обслуживаемого кода, для которого имеет значение быстродействие и надёжность. Он не предназначался для соревнований с интерпретируемыми скриптовыми языками, которые подходят для написания маленьких программ. JavaScript и другие подобные языки часто написаны на С++. Тем не менее, есть много полезных программ на С++, которые занимают всего несколько десятков или сотен строк.
Тут могут помочь авторы библиотек. Вместо того, чтобы концентрироваться на заумных и продвинутых вещах в библиотеках, предоставьте простые примеры “hello, world!”. Сделайте минимальную версию библиотеки, которую легко установить, и пример на одну страничку из того, что она умеет. В тот или иной момент времени мы все оказываемся в роли новичка. Кстати, вот моя версия “hello world” для С++:
Более длинные и сложные версии кажутся мне менее прикольными.
Часто у мифов есть основание. Каждому из них соответствуют моменты и ситуации, когда в них можно верить на разумном основании, основанном на доказательствах. На сегодняшний день я считаю их абсолютно ложными, простыми недоразумениями, хотя и полученными честным путём. Проблема в том, что мифы всегда служат какой-то цели, или они бы уже вымерли. Эти пять мифов служат разным целям:
— они дают комфорт. Не нужно ничего менять, переоценивать и переосмысливать. Знакомое кажется приятным. Перемены вызывают тревогу, поэтому хорошо, если новинка будет нежизнеспособной.
— можно сэкономить время. Если вам кажется, что вы знаете, что из себя представляет С++, вам не надо тратить время на изучение чего-либо нового, экспериментировать с новыми технологиями, измерять код на быстродействие, тренировать новичков.
— можно не учить С++. Если бы эти мифы были правдой, зачем его вообще нужно было бы учить?
— они помогают продвигать другие языки и технологии – в случае их правдивости это было бы необходимо.
Но они ложны, поэтому аргументы за то, чтобы сохранить всё, как есть, искать альтернативы С++ или избегать современного стиля программирования на нём, нельзя основывать на этих мифах. Существовать с устаревшим представлением о С++ в голове может и комфортно, но при работе с софтом необходимо меняться. Можно достичь большего, чем просто использовать С, С с классами, С++98 и т.д.
Приверженцы «старого, доброго» проигрывают. Затраты на поддержку часто больше, чем на написание современного кода. Старые компиляторы и инструменты обеспечивают меньшее быстродействие и проводят худший анализ, чем современные. Хорошие программисты часто отказываются от работы с антикварным кодом.
Современные версии С++ и технологии программирования, которые он поддерживает, отличаются в лучшую сторону от того представления, которое создают «общепризнанные мифы». Если вы верите в какие-то из них – не верьте мне на слово. Попробуйте, проверьте. Измерьте «старый способ» и альтернативы для актуальной проблемы. Попробуйте освоить новые методы, изучить новые возможности и технологии. Не забывайте сравнивать оценочную стоимость поддержки нового и старого способов. Лучший способ опровержения мифа – это представить доказательство. Я представил вам свои примеры и аргументы.
И я не заявляю, что С++ идеален. ��н не идеален, он не является наилучшим языком для всего и для всех. Как и любой другой язык. Воспринимайте его таким, какой он сейчас, а не каким он был 20 лет назад, и не таким, как его выставляет кто-то, кто рекламирует альтернативы. Чтобы сделать рациональный выбор, поищите достоверную информацию, и попробуйте сами понять, как современный С++ справляется с вашими задачами.
Не верьте «общепризнанному» знанию о С++, или бездоказательному его использованию. В этой статье рассматриваются пять популярных мнений о С++ и предлагаются аргументы в пользу того, что они – всего лишь мифы:
1. Чтобы понять С++, сначала нужно выучить С
2. С++ — это объектно-ориентированный язык программирования
3. В надёжных программах необходима сборка мусора
4. Для достижения эффективности необходимо писать низкоуровневый код
5. С++ подходит только для больших и сложных программ
Эти мифы вредны.
Остались сомнения? Сообщите мне, почему. Какие ещё мифы вы встречали? Почему они являются мифами, а не правдой? Какие у вас есть доказательства их разоблачения?
1. ISO/IEC 14882:2011 Programming Language C++
2. POCO libraries: pocoproject.org
3. Boost libraries: www.boost.org
4. AMP: C++ Accelerated Massive Parallelism. msdn.microsoft.com/en-us/library/hh265137.aspx
5. TBB: Intel Threading Building Blocks. www.threadingbuildingblocks.org
6. Cinder: A library for professional-quality creative coding. libcinder.org
7. vxWidgets: A Cross-Platform GUI Library. www.wxwidgets.org
8. Cgal — Computational Geometry Algorithms Library. www.cgal.org
9. Christopher Kohlhoff: Boost.Asio documentation. www.boost.org/doc/libs/1_55_0/doc/html/boost_asio.html
10. B. Stroustrup: Software Development for Infrastructure. Computer, vol. 45, no. 1, pp. 47-58, Jan. 2012, doi:10.1109/MC.2011.353.
11. Bjarne Stroustrup: The C++ Programming Language (4th Edition). Addison-Wesley. ISBN 978-0321563842. May 2013.
12. Bjarne Stroustrup: A Tour of C++. Addison Wesley. ISBN 978-0321958310. September 2013.
13. B. Stroustrup: Programming: Principles and Practice using C++ (2nd edition). Addison-Wesley. ISBN 978-0321992789. May 2014.
После публикации статьи на isocpp.org получили разные комментарии. Разрешите мне прокомментировать некоторые из них.
Комментарии подтвердили, что этот материал необходим. Люди повторяют старые аргументы. К сожалению, многие программисты не читают длинные статьи, а короткие отбрасывают за неполноту. Нежелание читать длинные ст��тьи побудило меня написать этот материал и разбить на три части при начальной публикации.
Это не исследовательский материал, который подробно описывает каждую деталь. Как я написал вначале: «Каждому мифу можно посвятить книгу, но я ограничусь простой констатацией и кратким изложением своих аргументов против них».
Тем не менее, многие путают примеры для иллюстрации точки зрения с самой точкой зрения. Некоторые пытались «опровергнуть опровержение», меняя примеры, меняя ограничения примеров, или объявляя примеры тривиальными. Примеры небольшие, т.к. они должны поместиться в небольшую работу. Но они не так уж отличаются от кода, который лежит в основе «настоящих» программ.
Некоторые комментаторы перешли с С++11/С++14, на которых я основывал свою аргументацию, к более старым версиям. С++14 – не С++ из 1980-х. Эти стандарты уже не такие, каким учились большинство людей. И не то, чему учатся на сегодняшних курсах. И не то, что люди видят, разглядывая довольно объёмные тексты существующих программ. Я хочу поменять это представление. Если вам не удаётся работать с моими примерами в какой-либо антикварной версии С++ или со старым компилятором – это плохо, но сегодня есть улучшенные версии всех основных компиляторов (и обычно бесплатные). В моих примерах не было никакого ультрасовременного кода.
Каждый язык программирования, достигающий успеха, сталкивается с проблемами старого кода. Не судите С++ по технологиям программирования 20-летней давности или компиляторам 10-летней давности. Взгляните на современный С++ и попробуйте воспользоваться новыми возможностями, как это уже удалось многим. Сегодня вы почти наверняка пользовались программой, написанной на С++11. Между моим и вашим компьютером очень много шагов, на которых встречаются программы на С++11.
Довольно много комментариев содержат заявления вроде «а в языке Х есть точно такая же возможность» или «библиотека Y в языке X делает именно это». Очевидно, если у вас есть язык, в котором проще, чем в С++, решить вашу задачу, при этом не теряя критично в быстродействии, переносимости и не приобретая ненужных ограничений – используйте его. Но ни один язык или библиотека не подходят идеально для всех и всего.
Я предоставил примеры для общих задачи и общих технологий. Сравнивать что-то с одним примером не особенно нужно. Моя точка зрения относится к общим вещам, а примеры – просто иллюстрации. При использовании достаточно хорошей библиотеки любой язык будет простым и приятным. Для достаточно ограниченной задачи можно сконструировать специальный язык, который будет элегантнее языка общего назначения. К примеру, библиотека asio, которую я использовал в пункте 6.1 – гибкая, эффективная сетевая библиотека общего назначения. Для любой задачи её можно обернуть в простую функцию (или небольшой набор функций), чтобы сделать её более удобной. И мой код был бы реализацией этого. То, что я пытался объяснить в п.6.2 – сообщество программистов С++ могли бы помочь программистам, проведя побольше времени над тем, чтобы делать простые вещи более простыми. Например, в 99% случаев я использую sort(v) вместо sort(v.begin(),v.end()).
Мои комментарии вызвали небольшую бурю. Многие пытались опровергать их простыми возражениями. Я не принимаю аргументы по быстродействию, не подкреплённые данными о тестировании. Мои комментарии были подтверждены реальными измерениями в разных ситуациях на протяжении нескольких лет. Многие из них описаны в книгах. Они правдивы для широкого спектра схожих примеров.
Я имею в виду современную реализацию С++, соответствующую стандартам. К примеру, когда я пишу про быстродействие оптимизации коротких строк, я не имею в виду реализации С++ до С++11. Я не рассматриваю комментарии на тему, что std::sort() или std::string работают медленно без использования оптимизатора. Разумеется – но глупо обсуждать быстродействие неоптимизированного кода. При использовании GCC или Clang используйте –O2; для продуктов от Microsoft используйте release mode.
Я неплохо знаю С и его стандартную библиотеку. Я написал много кода на С ещё до того, как сегодняшние студенты родились, и многое привнёс в язык: прототипирование, константы, инлайнинг, декларации в for, декларации как объявления, и многое другое. Я следил за его развитием и эволюционированием.
Да, C-версия compose() не проверяет значение, возвращаемое malloc(). Я же спрашивал у вас, всё ли я правильно сделал. Я намеренно не предоставил вам код, годный для продакшена. Отсутствие проверки результата – один из основных источников ошибок, поэтому моя «ошибка» специально была сделана для иллюстрации этого. В данном случае часто помогают исключения. Конечно, можно было написать С-версию compose(), используя менее известные функции стандартной библиотеки, и да, можно было избежать свободного хранения, если позволить вызывающему коду передать размещённый в стеке буфер и позволить вызывающему разбираться с проблемой строковых аргументов, которые бы его переполнили. Тем не менее, эти альтернативы не относятся к главному вопросу: такой код писать сложнее, чем в С++, и ещё сложнее писать его правильно. Новички с первого раза пишут версию для С++, но не для С, особенно для тех версий, которые основаны на функциях из стандартной библиотеки, которым новичков не обучают.
С++ использовался в критических и высоконагруженных встраиваемых системах годами – в тех же марсианских Роверах (анализ обстановки и автономная работа), F-35, F-16 (системы управления полётом), и множестве других: www.stroustrup.com/applications.html. И да, космическая капсула Орион запрограммирована при помощи С++.
Да, библиотеки разнятся по качеству, и иногда сложно выбрать нестандартную библиотеку из множества вариантов. Это проблема. Но эти библиотеки существуют, и их исследование часто более продуктивно, чем простое движение вперёд напролом, кончающееся изобретением очередного колеса.
К сожалению, часто библиотеки С++ не разрабатываются с учётом совместной работы с другими. И нет одного места, где можно было бы брать все библиотеки. Я годами наблюдал за процессом обучения студентов по схеме «сначала С», и читал эти программы десятилетиями. Тысячам людей я преподавал С++ в качестве первого языка. Мои заявления о возможности обучению С++ основаны на большом опыте.
С++ обучать легче, чем С из-за более хорошей системы типов и синтаксиса. Необходимо учить меньше трюков и костылей. Представьте, как бы вы стали учить стилю программирования на С, обучая языку С++. Я бы никогда не стал давать новичкам курс С++, который бы:
— не содержал хорошей основы касаемо работы с памятью, указателями, и т.д.
— не давал студентам представления о «чистом С» и о его использовании
— не обосновывал большинство возможностей языка
— пытался бы обучить абсолютно всем техникам С++
Хорошие учителя, преподающие С, не пытаются научить новичков всем техникам.
www.stroustrup.com/programming.html — мой ответ на вопрос «Как бы вы обучали новичков С++?». Эта система работает.
Можно ознакомиться с моей довольно старой работой по некоторым аспектам преподавания С и С++: Learning Standard C++ as a New Language. C/C++ Users Journal. pp 43-54. May 1999 (www.stroustrup.com/papers.html).
Сегодня я бы сделал С-версию курса получше, а С++ — сильно лучше. Примеры отражают стиль программирования того времени (и были рассмотрены экспертами по программированию на С и С++).
Сегодняшний С++ — это стандарт ISO С++14, а не то, что я описывал 30 лет назад, и не то, что ваш преподаватель рассказывал вам 20 лет назад. Изучите C++11/C++14 в том виде, в каком они поддерживаются основными компиляторами, и привыкните к ним. Это гораздо лучший инструмент, нежели ранние версии С++. Сегодняшний С – это стандарт ISO С11, а не K&R C (хотя я не уверен, соответствуют ли сегодняшние компиляторы стандарту С11 так же хорошо, как компиляторы С++ стандарту С++14). Меня шокируют некоторые вещи, которые сегодня преподают под видом «правильного С++».
С++ — это не ООП-язык. Это язык, поддерживающий ООП, другие техники программирования, и их комбинации. Если вы – опытный программист, я рекомендую прочесть A Tour of C++ в качестве быстрого обзора современного языка C++.
4.2 Разделённое владение shared_ptr
Не у каждого объекта может быть один владелец. Нам надо убедиться, что объект уничтожен и освобождён, когда исчезает последняя ссылка на него. Таким образом, нам необходима модель разделённого владения объектом. Допустим, у нас есть синхронная очередь, sync_queue, для общения между задачами. Отправитель и получатель получают по указателю на sync_queue:
void startup()
{
sync_queue* p = new sync_queue{200}; // опасность!
thread t1 {task1,iqueue,p}; // task1 читает из *iqueue и пишет в *p
thread t2 {task2,p,oqueue}; // task2 читает из *p и пишет в *oqueue
t1.detach();
t2.detach();
}
Предполагается, что task1, task2, iqueue и oqueue уже где-то были соответствующим образом определены и прошу прощения за то, что thread переживёт область видимости, где они были созданы (посредством detatch()). Вопрос: кто удалит sync_queue, созданные в startup()? Ответ: тот, кто последний будет использовать sync_queue. Это классический случай, когда требуется сборка мусора. Изначально сборка подсчитывала указатели: нужно хранить количество использований объекта, и в тот момент, когда счётчик обнуляется, удалять его. Множество современных языков работают так, а С++11 поддерживает эту идею через shared_ptr. Пример превращается в:
void startup()
{
auto p = make_shared<sync_queue>(200); // создать sync_queue и вернуть указатель stared_ptr на неё
thread t1 {task1,iqueue,p}; // task1 читает из *iqueue и пишет в *p
thread t2 {task2,p,oqueue}; // task2 читает из *p и пишет в *oqueue
t1.detach();
t2.detach();
}
Теперь деструкторы task1 и task2 могут уничтожить их shared_ptr (и в большинстве правильно построенных систем так и сделают), и последнее, что нужно сделать – уничтожить sync_queue. Это просто и довольно эффективно. Никакой сложной системы. Что важно, она не просто возвращает память, связанную с sync_queue. Она возвращает объект синхронизации (мьютекс, блокировку, что угодно), встроенный в sync_queue, чтобы синхронизировать две нити, выполняющие две задачи. Это не просто управление памятью, это управление ресурсами. Этот «скрытый» объект синхронизации обрабатывается так же, как хендлы файлов и потоков в предыдущем примере. Можно попробовать избавиться от использования shared_ptr, введя уникального владельца в какой-либо области видимости, заключающей в себе задачу, но это не всегда просто сделать – поэтому в С++11 есть и unique_ptr (для одиночного владения) and shared_ptr (для разделённого владения).
4.3 Типобезопасность
Я говорил пока о сборке мусора в контексте управления ресурсами. Но есть ещё и типобезопасность. У нас есть операция delete, которую можно применить неправильно. Пример:
X* p = new X;
X* q = p;
delete p;
// …
q->do_something(); // память, отведённая *p, могла быть перезаписана
Не надо так делать. Непосредственное применение delete опасно и не нужно в обычных случаях. Оставьте удаления классам, управляющим ресурсами — string, ostream, thread, unique_ptr и shared_ptr. Там удаления аккуратно отслеживаются.
4.4 Итог: идеалы управления ресурсами
С моей точки зрения, сборка мусора – последнее средство для управления ресурсами, а не решение задачи и не идеал.
1. Используйте подходящие абстракции, которые рекурсивно и неявно обслуживают свои ресурсы. Отдавайте предпочтение им, а не переменным в определённой области видимости.
2. Когда вам необходимо использовать указатели и ссылки, используйте умные указатели — unique_ptr и shared_ptr
3. Если ничего не помогает (например, ваш код – часть программы, которая запуталась в указателях, и не использует стратегию, поддерживаемую языком, для управления ресурсами и обработки ошибок), попробуйте обрабатывать ресурсы, не относящиеся к памяти, вручную, и включайте сборку мусора для обработки неизбежных утечек памяти.
5. Миф 4: для эффективности необходимо писать низкоуровневый код
Многие верят в то, что эффективный код обязан быть низкоуровневым. Некоторые даже верят, что низкоуровневый код обязательно эффективен. («Если оно такое уродливое, наверняка оно быстрое! Кто-то потратил кучу времени и своего таланта для создания этой штуковины!»). Конечно, можно писать эффективный код на низком уровне, и некоторый код необходимо делать низкоуровневым для работы с машинными ресурсами. Измеряйте, однако, стоит ли это ваших усилий. Современные компиляторы С++ очень эффективные, а архитектура современных машин чрезвычайно сложна. При необходимости такой низкоуровневый код необходимо прятать за интерфейсом для удобства. Часто сокрытие низкоуровневого кода за высокоуровневым интерф��йсом способствует оптимизации. Там, где важна эффективность, сначала попробуйте достичь её, выразив идею на высоком уровне, не кидайтесь сразу на биты и указатели.
5.1 qsort() в С
Простой пример. Если вам надо отсортировать набор чисел с плавающей точкой по убыванию, вы могли бы написать для этого код. Но если у вас нет экстремальных требований (например, чисел больше, чем может уместиться в памяти), это было бы наивно. За десятилетия мы сделали библиотеки с алгоритмами сортировки с приемлемой скоростью работы. Мне меньше всего нравится qsort() из стандартной ISO библиотеки C:
int greater(const void* p, const void* q) // трёхстороннее сравнение
{
double x = *(double*)p; // получить значение double с адреса p
double y = *(double*)q;
if (x>y) return 1;
if (x<y) return -1;
return 0;
}
void do_my_sort(double* p, unsigned int n)
{
qsort(p,n,sizeof(*p),greater);
}
int main()
{
double a[500000];
// … fill a …
do_my_sort(a,sizeof(a)/sizeof(*a)); // передать указатель и количество элементов
// …
}
Если вы не программируете на С, или если вы в последнее время не использовали qsort, потребуется кое-что объяснить; qsort принимает 4 аргумента
— указатель на последовательность байтов
— количество элементов
— размер элемента
— функция, сравнивающая два элемента, которые передаются как указатели на их первые байты
Этот интерфейс скрывает информацию. Мы сортируем не байты – мы сортируем double, но qsort этого не знает, поэтому нам надо предоставить информацию о том, как сравнивать double, и сколько байтов в double. Конечно, компилятор знает такие вещи. Но низкоуровневый интерфейс qsort не позволяет компилятору воспользоваться этой информацией. Необходимость указывать такую простую информацию ведёт к ошибкам. Не перепутал ли я два целых аргумента qsort? Если перепутаю, компьютер этого не заметит. Соответствует ли моя compare() соглашениям в C для трёхстороннего сравнения? Если вы посмотрите на промышленную реализацию qsort (рекомендую), вы увидите, сколько усилий приложено для компенсации недостатка информации. К примеру, довольно трудно произвести смену местами элементов, заданных в виде количества байт, чтобы это было так же эффективно, как смена местами пары double. Затратные непрямые вызовы функции сравнения могут быть устранены компилятором только в том случае, если он применит распространение констант для указателей на функции.
5.2 sort() в C++
Сравним qsort с его эквивалентом sort из С++
void do_my_sort(vector<double>& v)
{
sort(v,[](double x, double y) { return x>y; }); // сортировка v по убыванию
}
int main()
{
vector<double> vd;
// … fill vd …
do_my_sort(v);
// …
}
Здесь требуется меньше объяснений. Вектору известен его размер, и нам не надо явно указывать количество элементов. Тип элементов не теряется, и не нужно помнить об их размере. По умолчанию, sort сортирует по возрастанию, поэтому пришлось задать критерий сравнения, как и для qsort. Здесь он передан в качестве лямбда-выражения, сравнивающего два double при помощи >. И так получилось, что эта лямбда тривиальным образом инлайнится всеми компиляторами С++, что я знаю, поэтому сравнение превращается в одну машинную операцию «больше, чем» — никаких неэффективных вызовов функции.
Я использовал контейнерную версию sort, чтобы не задавать итераторы явно, то есть, чтобы не писать:
std::sort(v.begin(),v.end(),[](double x, double y) { return x>y; });
Можно пойти дальше и использовать объект сравнения С++14:
sort(v,greater<>()); // сортировка v по убыванию
Какая из версий быстрее? Можно скомпилировать версию qsort как С, так и С++ без всякий различий в быстродействии, поэтому это будет скорее сравнением стилей программирования, а не языков. Библиотечные реализации используют один алгоритм для sort и qsort, поэтому это сравнение стилей программирования, а не алгоритмов. Конечно, у разных библиотек и компиляторов будут разные результаты, но для каждой реализации будет видна разумная реакция на разные уровни абстракции.
Я недавно прогнал примеры, и увидел, что sort в 2.5 ��аза быстрее, чем qsort. Это может меняться от компилятора к компилятору и от компьютера к компьютеру, но ни разу у меня qsort не выиграл у sort. Иногда sort выполнялся в 10 раз быстрее. Почему? В стандартной библиотеке С++ sort явно выше уровнем, чем qsort, при этом более гибкий и общий. Он типобезопасен и параметризован на типе хранения, типе элементов и критерию сортировки. Никаких указателей, размеров, байтов. Библиотека STL, к которой принадлежит sort, старается не выбрасывать никакой информации. Это приводит к превосходному инлайнингу и хорошей оптимизации.
Обобщение и высокоуровневый код могут выигрывать у низкоуровневого. Не всегда, но сравнение sort/qsort – это не единичный пример. Всегда начинайте с высокогоуровневой, точной и типобезопасной версии решения. Оптимизируйте по необходимости.
6. Миф 5: С++ предназначен для больших и сложных программ
С++ — объёмный язык. Размер определений схож с С# и Java. Но это не значит, что вам нужно знать каждую деталь, чтобы использовать его, или использовать все функции непосредственно в каждой программе. Вот пример использования основных компонент из стандартной библиотеки:
set<string> get_addresses(istream& is)
{
set<string> addr;
regex pat { R"((\w+([.-]\w+)*)@(\w+([.-]\w+)*))"}; // шаблон е-мейл адреса
smatch m;
for (string s; getline(is,s); ) // прочесть строку
if (regex_search(s, m, pat)) // ищем шаблон
addr.insert(m[0]); // сохраняем адрес в наборе
return addr;
}
Предполагаю, что вы знакомы с регулярками. Если нет – самое время ознакомиться. Заметьте, что я полагаюсь на семантику перемещений, чтобы просто и эффективно вернуть потенциально большой набор строк. Все контейнеры стандартной библиотеки обеспечивают конструкторы перемещения, поэтому нет нужды возиться с new.
Для работы примера требуется включить компоненты:
#include<string>
#include<set>
#include<iostream>
#include<sstream>
#include<regex>
using namespace std;
Проверим:
istringstream test { // инициализируем поток строкой, содержащей адреса
"asasasa\n"
"bs@foo.com\n"
"ms@foo.bar.com$aaa\n"
"ms@foo.bar.com aaa\n"
"asdf bs.ms@x\n"
"$$bs.ms@x$$goo\n"
"cft foo-bar.ff@ss-tt.vv@yy asas"
"qwert\n"
};
int main()
{
auto addr = get_addresses(test); // get the email addresses
for (auto& s : addr) // write out the addresses
cout << s << '\n';
}
Просто пример. Легко можно поменять get_addresses(), чтобы она принимала регулярку как аргумент, чтобы она могла искать URL или что угодно. Легко поменять get_addresses(), чтоб она распознавала больше одного вхождения шаблона в строке. С++ предназначен для гибкости и обобщений, но не каждая программа обязана быть фреймворком. Суть в том, что задача извлечения емейлов из потока просто выражается и просто проверяется.
6.1 Библиотеки
На любом языке писать программу только через встроенные возможности языка (if, for, и +) утомительно. И наоборот, при наличии подходящих библиотек (graphics, route planning, database) любую задачу можно выполнить, приложив разумные усилия. Стандартная ISO библиотека С++ относительно небольшая (по сравнению с коммерческими), но помимо неё есть много библиотек как с исходным кодом, так и коммерческих. К примеру, при помощи библиотек Boost, POCO, AMP, TBB, Cinder, vxWidgets, CGAL сложные вещи становятся проще. К примеру, пусть наша программка извлекает URL с веб-страницы. Для начала, мы обобщим get_addresses() для поиска любой строки, совпадающей с шаблоном.
set<string> get_strings(istream& is, regex pat)
{
set<string> res;
smatch m;
for (string s; getline(is,s); ) // прочесть строку
if (regex_search(s, m, pat))
res.insert(m[0]); // сохранить совпадение в наборе
return res;
}
Это упрощённая версия. Теперь надо как-то прочесть файл из веба. В Boost есть библиотека asio для работы с вебом:
#include <boost/asio.hpp> // подключить boost.asio
Общение с веб-сервером довольно непростое:
int main()
try {
string server = "www.stroustrup.com";
boost::asio::ip::tcp::iostream s {server,"http"}; // установить соединение
connect_to_file(s,server,"C++.html"); // проверить и открыть файл
regex pat {R"((http://)?www([./#\+-]\w*)+)"}; // URL
for (auto x : get_strings(s,pat)) // ищем ссылки
cout << x << '\n';
}
catch (std::exception& e) {
std::cout << "Exception: " << e.what() << "\n";
return 1;
}
При разборе файла www.stroustrup.com/C++.html это даёт:
www-h.eng.cam.ac.uk/help/tpl/languages/C++.html
www.accu.org
www.artima.co/cppsource
www.boost.org
…
Я использовал множество, поэтому URL выводятся по алфавиту.
Я спрятал проверку соединения в connect_to_file():
void connect_to_file(iostream& s, const string& server, const string& file)
// открыть соединение с сервером и открыть файл в s
// пропустить заголовки
{
if (!s)
throw runtime_error{"нет соединения\n"};
// Запросить чтение файла с сервера
s << "GET " << "http://"+server+"/"+file << " HTTP/1.0\r\n";
s << "Host: " << server << "\r\n";
s << "Accept: */*\r\n";
s << "Connection: close\r\n\r\n";
// Проверить ответ:
string http_version;
unsigned int status_code;
s >> http_version >> status_code;
string status_message;
getline(s,status_message);
if (!s || http_version.substr(0, 5) != "HTTP/")
throw runtime_error{ "недопустимый ответ \n" };
if (status_code!=200)
throw runtime_error{ "код статуса в ответе " };
// Выбросить заголовки ответа, которые заканчиваются пустой строкой:
string header;
while (getline(s,header) && header!="\r");
}
Я не писал всё с нуля. Работа с HTTP скопирована с документации по asio.
6.2 Hello, World!
С++ — компилируемый язык, предназначающийся для создания хорошего, обслуживаемого кода, для которого имеет значение быстродействие и надёжность. Он не предназначался для соревнований с интерпретируемыми скриптовыми языками, которые подходят для написания маленьких программ. JavaScript и другие подобные языки часто написаны на С++. Тем не менее, есть много полезных программ на С++, которые занимают всего несколько десятков или сотен строк.
Тут могут помочь авторы библиотек. Вместо того, чтобы концентрироваться на заумных и продвинутых вещах в библиотеках, предоставьте простые примеры “hello, world!”. Сделайте минимальную версию библиотеки, которую легко установить, и пример на одну страничку из того, что она умеет. В тот или иной момент времени мы все оказываемся в роли новичка. Кстати, вот моя версия “hello world” для С++:
#include<iostream>
int main()
{
std::cout << "Hello, World\n";
}
Более длинные и сложные версии кажутся мне менее прикольными.
7 Применения мифов
Часто у мифов есть основание. Каждому из них соответствуют моменты и ситуации, когда в них можно верить на разумном основании, основанном на доказательствах. На сегодняшний день я считаю их абсолютно ложными, простыми недоразумениями, хотя и полученными честным путём. Проблема в том, что мифы всегда служат какой-то цели, или они бы уже вымерли. Эти пять мифов служат разным целям:
— они дают комфорт. Не нужно ничего менять, переоценивать и переосмысливать. Знакомое кажется приятным. Перемены вызывают тревогу, поэтому хорошо, если новинка будет нежизнеспособной.
— можно сэкономить время. Если вам кажется, что вы знаете, что из себя представляет С++, вам не надо тратить время на изучение чего-либо нового, экспериментировать с новыми технологиями, измерять код на быстродействие, тренировать новичков.
— можно не учить С++. Если бы эти мифы были правдой, зачем его вообще нужно было бы учить?
— они помогают продвигать другие языки и технологии – в случае их правдивости это было бы необходимо.
Но они ложны, поэтому аргументы за то, чтобы сохранить всё, как есть, искать альтернативы С++ или избегать современного стиля программирования на нём, нельзя основывать на этих мифах. Существовать с устаревшим представлением о С++ в голове может и комфортно, но при работе с софтом необходимо меняться. Можно достичь большего, чем просто использовать С, С с классами, С++98 и т.д.
Приверженцы «старого, доброго» проигрывают. Затраты на поддержку часто больше, чем на написание современного кода. Старые компиляторы и инструменты обеспечивают меньшее быстродействие и проводят худший анализ, чем современные. Хорошие программисты часто отказываются от работы с антикварным кодом.
Современные версии С++ и технологии программирования, которые он поддерживает, отличаются в лучшую сторону от того представления, которое создают «общепризнанные мифы». Если вы верите в какие-то из них – не верьте мне на слово. Попробуйте, проверьте. Измерьте «старый способ» и альтернативы для актуальной проблемы. Попробуйте освоить новые методы, изучить новые возможности и технологии. Не забывайте сравнивать оценочную стоимость поддержки нового и старого способов. Лучший способ опровержения мифа – это представить доказательство. Я представил вам свои примеры и аргументы.
И я не заявляю, что С++ идеален. ��н не идеален, он не является наилучшим языком для всего и для всех. Как и любой другой язык. Воспринимайте его таким, какой он сейчас, а не каким он был 20 лет назад, и не таким, как его выставляет кто-то, кто рекламирует альтернативы. Чтобы сделать рациональный выбор, поищите достоверную информацию, и попробуйте сами понять, как современный С++ справляется с вашими задачами.
8 Итог
Не верьте «общепризнанному» знанию о С++, или бездоказательному его использованию. В этой статье рассматриваются пять популярных мнений о С++ и предлагаются аргументы в пользу того, что они – всего лишь мифы:
1. Чтобы понять С++, сначала нужно выучить С
2. С++ — это объектно-ориентированный язык программирования
3. В надёжных программах необходима сборка мусора
4. Для достижения эффективности необходимо писать низкоуровневый код
5. С++ подходит только для больших и сложных программ
Эти мифы вредны.
9 Обратная связь
Остались сомнения? Сообщите мне, почему. Какие ещё мифы вы встречали? Почему они являются мифами, а не правдой? Какие у вас есть доказательства их разоблачения?
10 Ссылки
1. ISO/IEC 14882:2011 Programming Language C++
2. POCO libraries: pocoproject.org
3. Boost libraries: www.boost.org
4. AMP: C++ Accelerated Massive Parallelism. msdn.microsoft.com/en-us/library/hh265137.aspx
5. TBB: Intel Threading Building Blocks. www.threadingbuildingblocks.org
6. Cinder: A library for professional-quality creative coding. libcinder.org
7. vxWidgets: A Cross-Platform GUI Library. www.wxwidgets.org
8. Cgal — Computational Geometry Algorithms Library. www.cgal.org
9. Christopher Kohlhoff: Boost.Asio documentation. www.boost.org/doc/libs/1_55_0/doc/html/boost_asio.html
10. B. Stroustrup: Software Development for Infrastructure. Computer, vol. 45, no. 1, pp. 47-58, Jan. 2012, doi:10.1109/MC.2011.353.
11. Bjarne Stroustrup: The C++ Programming Language (4th Edition). Addison-Wesley. ISBN 978-0321563842. May 2013.
12. Bjarne Stroustrup: A Tour of C++. Addison Wesley. ISBN 978-0321958310. September 2013.
13. B. Stroustrup: Programming: Principles and Practice using C++ (2nd edition). Addison-Wesley. ISBN 978-0321992789. May 2014.
Послесловие
После публикации статьи на isocpp.org получили разные комментарии. Разрешите мне прокомментировать некоторые из них.
Комментарии подтвердили, что этот материал необходим. Люди повторяют старые аргументы. К сожалению, многие программисты не читают длинные статьи, а короткие отбрасывают за неполноту. Нежелание читать длинные ст��тьи побудило меня написать этот материал и разбить на три части при начальной публикации.
Это не исследовательский материал, который подробно описывает каждую деталь. Как я написал вначале: «Каждому мифу можно посвятить книгу, но я ограничусь простой констатацией и кратким изложением своих аргументов против них».
Тем не менее, многие путают примеры для иллюстрации точки зрения с самой точкой зрения. Некоторые пытались «опровергнуть опровержение», меняя примеры, меняя ограничения примеров, или объявляя примеры тривиальными. Примеры небольшие, т.к. они должны поместиться в небольшую работу. Но они не так уж отличаются от кода, который лежит в основе «настоящих» программ.
Некоторые комментаторы перешли с С++11/С++14, на которых я основывал свою аргументацию, к более старым версиям. С++14 – не С++ из 1980-х. Эти стандарты уже не такие, каким учились большинство людей. И не то, чему учатся на сегодняшних курсах. И не то, что люди видят, разглядывая довольно объёмные тексты существующих программ. Я хочу поменять это представление. Если вам не удаётся работать с моими примерами в какой-либо антикварной версии С++ или со старым компилятором – это плохо, но сегодня есть улучшенные версии всех основных компиляторов (и обычно бесплатные). В моих примерах не было никакого ультрасовременного кода.
Каждый язык программирования, достигающий успеха, сталкивается с проблемами старого кода. Не судите С++ по технологиям программирования 20-летней давности или компиляторам 10-летней давности. Взгляните на современный С++ и попробуйте воспользоваться новыми возможностями, как это уже удалось многим. Сегодня вы почти наверняка пользовались программой, написанной на С++11. Между моим и вашим компьютером очень много шагов, на которых встречаются программы на С++11.
Довольно много комментариев содержат заявления вроде «а в языке Х есть точно такая же возможность» или «библиотека Y в языке X делает именно это». Очевидно, если у вас есть язык, в котором проще, чем в С++, решить вашу задачу, при этом не теряя критично в быстродействии, переносимости и не приобретая ненужных ограничений – используйте его. Но ни один язык или библиотека не подходят идеально для всех и всего.
Я предоставил примеры для общих задачи и общих технологий. Сравнивать что-то с одним примером не особенно нужно. Моя точка зрения относится к общим вещам, а примеры – просто иллюстрации. При использовании достаточно хорошей библиотеки любой язык будет простым и приятным. Для достаточно ограниченной задачи можно сконструировать специальный язык, который будет элегантнее языка общего назначения. К примеру, библиотека asio, которую я использовал в пункте 6.1 – гибкая, эффективная сетевая библиотека общего назначения. Для любой задачи её можно обернуть в простую функцию (или небольшой набор функций), чтобы сделать её более удобной. И мой код был бы реализацией этого. То, что я пытался объяснить в п.6.2 – сообщество программистов С++ могли бы помочь программистам, проведя побольше времени над тем, чтобы делать простые вещи более простыми. Например, в 99% случаев я использую sort(v) вместо sort(v.begin(),v.end()).
Быстродействие
Мои комментарии вызвали небольшую бурю. Многие пытались опровергать их простыми возражениями. Я не принимаю аргументы по быстродействию, не подкреплённые данными о тестировании. Мои комментарии были подтверждены реальными измерениями в разных ситуациях на протяжении нескольких лет. Многие из них описаны в книгах. Они правдивы для широкого спектра схожих примеров.
Я имею в виду современную реализацию С++, соответствующую стандартам. К примеру, когда я пишу про быстродействие оптимизации коротких строк, я не имею в виду реализации С++ до С++11. Я не рассматриваю комментарии на тему, что std::sort() или std::string работают медленно без использования оптимизатора. Разумеется – но глупо обсуждать быстродействие неоптимизированного кода. При использовании GCC или Clang используйте –O2; для продуктов от Microsoft используйте release mode.
Я неплохо знаю С и его стандартную библиотеку. Я написал много кода на С ещё до того, как сегодняшние студенты родились, и многое привнёс в язык: прототипирование, константы, инлайнинг, декларации в for, декларации как объявления, и многое другое. Я следил за его развитием и эволюционированием.
Да, C-версия compose() не проверяет значение, возвращаемое malloc(). Я же спрашивал у вас, всё ли я правильно сделал. Я намеренно не предоставил вам код, годный для продакшена. Отсутствие проверки результата – один из основных источников ошибок, поэтому моя «ошибка» специально была сделана для иллюстрации этого. В данном случае часто помогают исключения. Конечно, можно было написать С-версию compose(), используя менее известные функции стандартной библиотеки, и да, можно было избежать свободного хранения, если позволить вызывающему коду передать размещённый в стеке буфер и позволить вызывающему разбираться с проблемой строковых аргументов, которые бы его переполнили. Тем не менее, эти альтернативы не относятся к главному вопросу: такой код писать сложнее, чем в С++, и ещё сложнее писать его правильно. Новички с первого раза пишут версию для С++, но не для С, особенно для тех версий, которые основаны на функциях из стандартной библиотеки, которым новичков не обучают.
С++ использовался в критических и высоконагруженных встраиваемых системах годами – в тех же марсианских Роверах (анализ обстановки и автономная работа), F-35, F-16 (системы управления полётом), и множестве других: www.stroustrup.com/applications.html. И да, космическая капсула Орион запрограммирована при помощи С++.
Библиотеки
Да, библиотеки разнятся по качеству, и иногда сложно выбрать нестандартную библиотеку из множества вариантов. Это проблема. Но эти библиотеки существуют, и их исследование часто более продуктивно, чем простое движение вперёд напролом, кончающееся изобретением очередного колеса.
К сожалению, часто библиотеки С++ не разрабатываются с учётом совместной работы с другими. И нет одного места, где можно было бы брать все библиотеки. Я годами наблюдал за процессом обучения студентов по схеме «сначала С», и читал эти программы десятилетиями. Тысячам людей я преподавал С++ в качестве первого языка. Мои заявления о возможности обучению С++ основаны на большом опыте.
С++ обучать легче, чем С из-за более хорошей системы типов и синтаксиса. Необходимо учить меньше трюков и костылей. Представьте, как бы вы стали учить стилю программирования на С, обучая языку С++. Я бы никогда не стал давать новичкам курс С++, который бы:
— не содержал хорошей основы касаемо работы с памятью, указателями, и т.д.
— не давал студентам представления о «чистом С» и о его использовании
— не обосновывал большинство возможностей языка
— пытался бы обучить абсолютно всем техникам С++
Хорошие учителя, преподающие С, не пытаются научить новичков всем техникам.
www.stroustrup.com/programming.html — мой ответ на вопрос «Как бы вы обучали новичков С++?». Эта система работает.
Можно ознакомиться с моей довольно старой работой по некоторым аспектам преподавания С и С++: Learning Standard C++ as a New Language. C/C++ Users Journal. pp 43-54. May 1999 (www.stroustrup.com/papers.html).
Сегодня я бы сделал С-версию курса получше, а С++ — сильно лучше. Примеры отражают стиль программирования того времени (и были рассмотрены экспертами по программированию на С и С++).
Сегодняшний С++ — это стандарт ISO С++14, а не то, что я описывал 30 лет назад, и не то, что ваш преподаватель рассказывал вам 20 лет назад. Изучите C++11/C++14 в том виде, в каком они поддерживаются основными компиляторами, и привыкните к ним. Это гораздо лучший инструмент, нежели ранние версии С++. Сегодняшний С – это стандарт ISO С11, а не K&R C (хотя я не уверен, соответствуют ли сегодняшние компиляторы стандарту С11 так же хорошо, как компиляторы С++ стандарту С++14). Меня шокируют некоторые вещи, которые сегодня преподают под видом «правильного С++».
С++ — это не ООП-язык. Это язык, поддерживающий ООП, другие техники программирования, и их комбинации. Если вы – опытный программист, я рекомендую прочесть A Tour of C++ в качестве быстрого обзора современного языка C++.
