Попробуем теперь использовать получившуюся программу рисования диаграммы наследования с реальным кодом, т.е. взятым из реального мира. Вопрос в том, какой именно код взять? Лучший кандидат для проверки - наша программа рисования, testclang.cpp! Что может быть реальнее? :)
— Потому что ты тут, — просто сказал Кот. — Иначе бы ты сюда не попала.
21 марта 2016 г.
10 февраля 2016 г.
CLang API: рисуем диаграмму наследования (clang-3.7)
Попробуем использовать CLang API для чего-нибудь полезного, например, для рисования диаграммы наследования.
В Clang API уже есть подобный функционал - функция CXXRecordDecl::viewInheritance(). Функция viewInheritance рисует диаграмму наследования для заданного класса используя библиотеку GraphViz. Но делает она это не совсем так, как мне бы хотелось. Поэтому я попробую написать свою собственную процедуру рисования диаграммы наследования, используя программу dot из пакета GraphViz.
3 февраля 2016 г.
CLang API: обход AST (clang-3.7)
Попробуем теперь реализовать обход AST встроенными методами (по мотивам статьи "How to write RecursiveASTVisitor based ASTFrontendActions"). На мой взгляд, реализация этой задачи стала намного проще по сравнению с версией 2.8.
20 января 2016 г.
CLang API: минимальный проект (clang-3.7)
После долгого перерыва я решил вернуться к попыткам использования clang API по нескольким причинам. Во-первых, clang API за это время продвинулся довольно далеко и стал "мощнее", так что хочется попробовать новые версии. Во-вторых, в старых статьях используется более ранние версии clang, а мне регулярно задают вопросы типа "почему у меня не компилируется/линкуется", поэтому хочется и обновить свои знания и добавить статьям актуальности. В-третьих, за пять лет внятных примеров с использованием clang так и не появилось, надо исправить такую несправедливость. Ну и в конце концов меня не оставляет надежда реализовать одну идею со статическим анализом кода, о которой, может быть, напишу позже.
Итак, попробуем написать и собрать минимальный проект для clang API на платформе Linux, используя последнюю стабильную версию llvm и clang. На момент написания статьи это версия 3.7.0.
11 июня 2013 г.
Десять очень полезных возможностей C++11
Хорошая статья на Хабре: Десять возможностей C++11, которые должен использовать каждый C++ разработчик. Название, правда, слегка преувеличенное, хотя менее полезной статья от этого не становится.
В статье обсуждаются такие возможности как:
- auto
- nullptr
- range-based циклы
- override и final
- строго-типизированный enum
- интеллектуальные указатели
- лямбды
- non-member begin() и end()
- static_assert и классы свойств
- семантика перемещения
25 августа 2011 г.
Наши герои
ЗЫ. Что-то я не смог вставить сюда HTML5, так что вот iframe.
13 августа 2011 г.
Саттер рапортует: Стандарт одобрен!
7 апреля 2011 г.
Саттер рапортует
Как сообщает Саттер черновик стандарта C++ принят в окончательной редакции. Если всё будет хорошо, то стандарт выйдет летом 2011.
27 декабря 2010 г.
Нововведения в C++0x, которые не могут не радовать (меня)
Просмотрел повнимательнее список новшеств в будущем стандарте C++0x и обнаружил, что многие грабли, которые приходилось раньше реализовывать самостоятельно или городить воркэраунды, теперь будут поддерживаться конструкциями языка или добавлены в стандартную библиотеку. Это не может не радовать, потому что это сильно облегчает жизнь.
- Первым пунктом у меня идут потоки и объекты синхронизации (§30 [thread]):
thread,mutex,lock,condition_variable,future, ... Наконец-то! Надеюсь, теперь можно будет выкинуть весь самописный код, который реализовывал классы с подобной функциональностью для использования на различных платформах. Конечно, было бы неплохо расширить список поддерживаемых Стандартом объектов синхронизации, но и существующий список вполне хорош. static_assert— проверка условий на этапе компиляции, которой так не хватало в предыдущих версиях Стандарта. Очень полезна для проверки размеров типов данных и кратности этих типов каким-то заданным значениям. (§7 [dcl.dcl])- Списки инициализации и универсальная инициализация — новая синтаксическая конструкция (§8.5.4 [dcl.init.list]), которая позволяет инициализировать массивы, структуры данных и контейнеры следующим образом:
double ad[] = { 1, 2.0 };
S2 s22 { 1.0, 2, 3 };
std::map<std::string, int> anim =
{ {"bear",4}, {"cassowary",2}, {"tiger",7} };
shared_ptr— умные указатели (§20.9.11 [util.smartptr]). Объект, хранимый умным указателем, уничтожается только после того, как счетчик ссылок на объект будет равен нулю.- Лямбда-выражения (§5.1.2 [expr.prim.lambda]). К самим по себе лямбда-выражениям я отношусь достаточно спокойно. Даже, пожалуй, скептически, потому что мне кажется, что фанатичное использование лямбда-выражений приведёт к хаосу (особенно выражений, которые изменяют состояние классов). Но у лямбда-выражений есть свойство, которого мне раньше не хватало — их можно передавать в классы и алгоритмы стандартной библиотеки (и в пользовательские шаблоны) в качестве предикатов. Раньше надо было городить кучу кода, определяя дополнительные функции и классы, причем передавать в шаблон локальный класс запрещено. А с новым стандартом можно сделать так:
std::sort(x, x + N,
[](float a, float b)
{ return std::abs(a) < std::abs(b); });
- Автоматический тип данных
auto— тип объявляемой переменной определяется самостоятельно из типа инициализатора. Особенно удобно использовать при работе с итераторами контейнеров и другими типами, от которых зависит шаблон, чтобы не париться с горой typedef'ов. (§7.1.6.4 [dcl.spec.auto]) - Возможность вызова конструкторов класса из других конструкторов этого же класса (§12.6.2 [class.base.init]). Очень сильно облегчает написание конструкторов классов, помогает избежать дублирования кода инициализации.
- Цикл
forпо коллекциям (§6.5.4 [stmt.ranged]). Не очень актуально, учитывая появлениеautoи лямбда-выражений, но объём кода уменьшает.
int array[5] = { 1, 2, 3, 4, 5 };
for (int& x : array)
x *= 2;
- Из маленьких приятных мелочей можно упомянуть поддержку генераторов случайных чисел (§26.5 [rand]), атомарных операций (§29 [atomics]), регулярных выражений (§28 [re]) и новых алгоритмов. Например, алгоритм
iota(§27.6.5 [numeric.iota]), который заполняет заданный интервал инкрементирующимися значениями, начиная с заданного (подобный функционал я реализовывал с помощью алгоритма generate и самописного класса UniqueNumberGenerator).
9 декабря 2010 г.
Саттер рапортует
Было:
class [[base_check]] Derived : public Base {
public:
virtual void f [[override]] ();
virtual double g [[final]] ( int );
virtual void h [[hiding]] ();
};Стало:
class Derived explicit : public Base {
public:
virtual void f () override;
virtual double g( int ) final;
virtual void h() new;
};
STL: есть ли жизнь без исключений
-fno-exceptions в GCC, или /EHs- в Visual Studio). Однако, отключение обработки исключений вовсе не означает, что программа не будет генерировать исключения. Это всего лишь означает, что компилятор не будет создавать код для обработки исключений и уничтожения автоматических объектов. А исключения будут продолжать генерироваться — операторами new при нехватке памяти, методом at() класса std::vector при выходе за пределы массива, просто операторами throw расставленными программистом по своему коду terminate().Классы стандартной библиотеки сообщают об ошибках только с помощью исключений и никак иначе. Проблема использования STL в том, что когда мы отключаем поддержку исключений в проекте, то лишаемся механизма обработки ошибок. И если проблему с оператором
new можно решить его заменой на new (nothrow), то исключение, генерируемое в аллокаторе при вызове vector::resize(), вроде как ничем не заменишь.6 декабря 2010 г.
Clang API: обход AST (clang-2.8)
26 ноября 2010 г.
Clang API: получение AST в виде XML
Задача: с помощью Clang API получить XML-файл, описывающий абстрактное синтаксическое дерево (AST) исходного кода. Это можно сделать и простым запуском команды
clang с параметром -ast-print-xml в командной строке, но через API — интереснее :)24 ноября 2010 г.
Исключения C++ и производительность (2)
Итак, я знаю, что в общем случае при обработке сгенерированного исключения происходят дополнительные затраты процессорного времени. Какие они и чем мне это грозит?
23 ноября 2010 г.
Исключения C++ и производительность (1.1)
Наверное, нужно упомянуть предстоящие изменения в стандарте C++. Херб Саттер, который уже лет десять пытается бороться со спецификаторами
throw(), все-таки добился того, чтобы в новом стандарте спецификаторы throw(A, B, ...) считались устаревшими, а спецификаторы throw() были заменены на новый спецификатор noexcept именно с той целью, чтобы компиляторы могли выполнять оптимизацию, если возможно.Лично я считаю, что как спецификатор не назови (
throw или noexcept), то компилятору всё равно. Для программиста — да, noexcept куда более читабельно, но смысл при этом не меняется. Что касается спецификаторов
throw(A, B, ...), то если их все-таки упразднят в окончательной версии стандарта — то и бог с ними, если нет — тоже не страшно. Но мне интересно почему спецификаторы throw(A, B, ...) не использовались для углубленного статического анализа кода компиляторами, это ведь было возможно?.. Вместо того, чтобы их ругать, их можно было превратить в оружие. Ну да ладно, до принятия нового стандарта еще дожить надо :)Продолжение: Исключения C++ и производительность (2)
16 ноября 2010 г.
Исключения C++ и производительность (1)
Существует очень много книг с советами, как лучше программировать на С++, и все они рекомендуют использовать исключения C++ для обработки ошибок. Возможно, есть книги, в которых не рекомендуется использовать исключения, но я таких не встречал. Сейчас как раз дочитываю "Чистый код" Роберта Мартина, и в разделе "Обработка ошибок" опять встретил этот настоятельный совет. Мотивация — использование исключений делает код более чистым и понятным. С такой формулировкой трудно поспорить, но жаль, что авторы таких советов никак не поясняют в своих книгах, в каких случаях использовать исключения, а в каких лучше не использовать.
Я встречал две крайности: разработчики, которые используют исключения везде, и разработчики, которые вообще не используют исключений. В первом случае объяснением было "потому что это круто правильно и объектно-ориентированно", в другом — "потому что они дают слишком большой оверхэд". И я задумался, кто из них прав? И, вообще, какова цена использования исключений в C++? Можно ли как-то управлять этим? Может, стоит выработать для себя какие-то правила?..
25 октября 2010 г.
Битхаки
30 апреля 2009 г.
suncc в качестве подручного инструмента
Наверное, самый лучший способ достичь этого — изучать алгоритмы и приемы оптимизации кода. Но будь ты хоть семи пядей во лбу, когда рядом есть "подручные средства" — грех не воспользоваться ими.
Я хочу рассказать, как использовать компилятор Sun для того, чтобы повысить производительность вашего кода. У этого компилятора есть замечательная особенность — при выполнении оптимизации он может вставлять свои комментарии в объектные файлы, которые можно потом использовать для дальнейшего улучшения кода. Компилятор Sun прилагается к пакету Sun Studio, который является бесплатным и доступен для скачивания с http://developers.sun.com
В качестве примера рассмотрим простую программу:
int main ()
{
int c = 0;
int size = 10000;
int *a = new int[size * size];
for (int i = 0; i < size; ++i)
{
for (int j = 0; j < size; ++j)
{
a[j * size + i] = c++;
}
}
delete [] a;
return 0;
}
Эта программа создает квадратную матрицу размером size на size, и заполняет ее числами от 0 до N по столбцам. Для матрицы 10 на 10 это будет выглядеть так:
0 10 20 30 40 50 60 70 80 90
1 11 21 31 41 51 61 71 81 91
2 12 22 32 42 52 62 72 82 92
3 13 23 33 43 53 63 73 83 93
4 14 24 34 44 54 64 74 84 94
5 15 25 35 45 55 65 75 85 95
6 16 26 36 46 56 66 76 86 96
7 17 27 37 47 57 67 77 87 97
8 18 28 38 48 58 68 78 88 98
9 19 29 39 49 59 69 79 89 99
Проход по столбцам матрицы заведомо неэффективен, поэтому на такой программе должны быть хорошо видны результаты оптимизации. Попробуем замерить усредненное время выполнения программы для size=10000, скомпилированной с помощью
g++ и sunCC:g++ без оптимизации (g++ -O0 a.cpp): 2.6 сек
g++ c оптимизацией (g++ -O2 a.cpp): 2.0 сек
sunCC без оптимизации (sunCC a.cpp): 3.2 сек
sunCC c оптимизацией (sunCC -fast a.cpp): 0.4 сек
Опция
-fast в suncc анализирует окружение и подбирает набор опций компилятора для максимального быстродействия. Видно, что sunCC в режиме оптимизации дает результаты в разы лучше, чем g++. А именно — в 5 раз. Хм...Цель данной статьи совсем не в том, чтобы принизить возможности GCC. Для замеров использовался GCC версии 4.1. На данный момент существуют более новые версии этого компилятора, которые могут показать лучшие результаты оптимизации этого кода.
Давайте посмотрим, что же делает sunCC при оптимизации. Для этого необходимо использовать при компиляции опцию
-g0. Эта опция сохраняет в объектном файле отладочную информацию (в том числе и комментарии компилятора), но не запрещает инлайнить функции. Для просмотра комментариев нужно использовать программу er_src, которая также поставляется с Sun Studio.
$ sunCC -g0 -fast a.cpp
$ er_src a.o
Source file: ./a.cpp
Object file: ./a.o
Load Object: ./a.o
1. int main ()
2. {
3. int c = 0;
4. int size = 10000;
5. int *a = new int[size * size];
6.
Source loop below has tag L1
Induction variable substitution performed on L1
L1 interchanged with L2
7. for (int i = 0; i < size; ++i)
8. {
Source loop below has tag L2
Induction variable substitution performed on L2
L2 interchanged with L1
9. for (int j = 0; j < size; ++j)
10. {
11. a[j * size + i] = c++;
12. }
13. }
14.
15. delete [] a;
16. return 0;
17. }
Итак, что же мы видим? Компилятор обнаружил в программе два цикла L1 и L2, которые поддаются оптимизации, о чем и сообщил в комментариях. Давайте попробуем улучшить программу, используя подсказки компилятора.
Подсказка "Induction variable substitution performed" говорит нам о том, что компилятор обнаружил индукционную переменную и произвел ее замену. Это значит, что значение какой-то переменной может быть вычислено через значения итераторов циклов. После недолгого изучения кода заменяем строку "
a[j * size + i] = c++;a[j * size + i] = i * size + j;Видим, что сообщение об индукционной переменной пропало, но осталось другое сообщение: "L1 interchanged with L2". Это сообщение говорит нам о том, что компилятор поменял циклы местами, то есть заменил проход по столбцам проходом по строкам матрицы. Меняем циклы местами и снова прогоняем компиляцию. И убеждаемся, что сообщения о том, что компилятор что-то оптимизировал, пропали. То есть, оптимизировать больше нечего.
После наших изменений программа приняла вот такой вид:
int main ()
{
int c = 0;
int size = 10000;
int *a = new int[size * size];
for (int j = 0; j < size; ++j)
{
for (int i = 0; i < size; ++i)
{
a[j * size + i] = i * size + j;
}
}
delete [] a;
return 0;
}
Давайте теперь замерим время выполнения программы после всех наших изменений:
g++ без оптимизации (g++ -O0 a.cpp): 1.0 сек
g++ c оптимизацией (g++ -O2 a.cpp): 0.4 сек
sunCC без оптимизации (sunCC a.cpp): 0.6 сек
sunCC c оптимизацией (sunCC -fast a.cpp): 0.4 сек
На мой взгляд, получилось просто прекрасно! :)
Подробнее почитать о комментариях компилятора Sun можно здесь: http://docs.sun.com/app/docs/doc/819-5264/afapn?a=view
3 апреля 2009 г.
Кодоформатная паранойя
Давным-давно программисты на Си при объявлении и определении функций не ставили пробелы между именем функции и открывающей круглой скобкой, а при вызове функций — ставили.
Для чего? Для того, чтобы grep'ом можно было легко найти или все вызовы функции или ее объявления.
Почему именно так ставили пробелы, а не наоборот? Потому, что иначе с define'ами была бы проблема. Ведь не напишешь же — "
#define FUNC (A) ...". Только так — "#define FUNC(A) ...".Возможно, некоторые моменты в этой истории спорные, но в общем история кажется мне вполне разумной.
1 апреля 2009 г.
Предупреждения GCC
В дополнение к этому, в версии 4.3 появилось предупреждение о том, что объявление вида '
char *str = "abcd";' устарело и больше не должно использоваться. Причем предупреждение появляется даже без флагов -Wall, -Wextra и прочих. Как известно, использование в программе таких объявлений может привести к непредсказуемым последствиям. Вместо этого нужно использовать 'const char *str = "abcd";'. И это правильно.UPD: Несмотря на дату, это не шутка :)