Гримуар C++
или N вещей, которые вы могли не видеть в C++ коде
или Как запугать жуниора
В коде на C++ можно увидеть много компиляторо-специфичных или просто редко встречающихся в учебниках штук, смысл которых туманен, а как их гуглить, иногда бывает неясно. К примеру, когда я, будучи еще школьником, переходил с бейсика на C++ и стремительно овладевал непростым понятием цикла for, поля класса и объявления переменных, мне встретился забористый кусок С++-кода с ::, namespace, template< template<> class >, что привело к поломке моего парсера :)
Сначала разминка
1. В списке объявления переменных астериски и амперсанды относятся к переменной, а не к типу.
2. В C++ довольно хитрые области видимости.
3. Оператор стремления :)
На самом деле, конечно, всего-лишь постдекремент и >.
4. В инварианте цикла можно объявлять переменные,
впрочем, как и в условиях.
5. А в инициализации можно объявлять типы.
6. Всем известное устройство Даффа.
Умные слова switch и case не должны скрывать от вас тот факт, что внутре у него goto.
7. В C++ есть несколько способов полностью обнулить структуру.
8. Чтобы упростить копипасту в инициализации массивов, в конце может стоять запятая.
9. Эта же запятая может стать источником проблем, если она перегружена. Если мы хотим этого избежать, используется такой трюк:
Например, в таком коде первый цикл исполняется с побочным эффектом:
10. Тернарный оператор правоассоциативен и может выступать в качестве lvalue. Такой код сделает именно то, что должен, присвоит b = 5:
11. А еще он может возвращать void и кидать исключения (это особенно полезно в списках инициализации классов). Если же в его результатах два разных типа, то берется более общий.
12. Программисты на C++ часто забывают о битовых полях. А ведь их размеры могут задаваться аргументами шаблона. И еще у них есть поля-разделители (должны быть безымянными, выравнивают следующее поле по allocation unit boundary).
Я слышал, что у языка C++ есть какой-то безумный предшественник, называемый просто C. Вот, что они используют в ядре Linux вместо static_assert:
А еще вот такое.
13. Сначала я думал, что боян и все знают, но оказалось, что не все. Деструктор может быть чисто виртуальным, но при этом иметь тело. Но его нельзя определить сразу же.
14. Многоточие и макросы для взаимодействия с ним:
15. Function-try-block.
16. Диграфы и триграфы, еще.
В частности, из-за диграфов в Boost всегда пишут < :: вместо <::
17. Булевы операторы можно записать словами.
Пристально смотрите на слова and, and_eq, not, not_eq, or, or_eq, xor, xor_eq, bitand, bitor, compl.
Visual C++ требует для их работы
18. Иногда компилятору нужно словами объяснить, что он перед собой видит. Ключевыми словами operator, typename и template.
Кстати, Visual Studio 2013 не переносит тут слова template перед operator, а GCC и Clang наоборот, его отсутствия. ++vc_bugs_discovered_by_me;
19. Некоторые объявления могут казаться немного запутанными. Следует одолеть правило чтения по спирали (или тут).

Еще можно для расширения кругозора почитать про синтаксис SPECS, предложенный для упрощения подобного кода.
20. Спецификация исключений. Самая, пожалуй, редко используемая возможность C++ (привет, Яндекс! :)), позволяющая указывать, что может выкинуть функция, карая нарушителей std::unexpected и std::terminate. Была беспощадно выпилена в C++11 в пользу более узкого noexcept.
21. Насколько извращенным может быть обычный new? В хорошем коде на C++ он почти не встречается вовсе, так что вы вполне могли не видеть чего-то вроде
Первый пример это placement new[] c new-initializer, создающий обнуленный массив из 4 интов на месте буфера buff. Второй обычный new[], но мы просим его не кидать исключений. Об этом и многом другом читайте 5.3.4 [expr.new] :)
22. Думаете, такой код не скомпилируется? Выдаст строку?
На самом деле он выведет 1179604306 (на моей машине). Да, это ROUF, если смотреть побайтово и multicharacter literal, если смотреть в Стандарте. Удобно для всяких магических чисел типа FourCC.
23. А еще среди тяжкого наследия есть такая функция printf.
Впрочем, последнее не работает нигде, кроме богомерзких юниксов, а заботливая Visual Studio отключает по умолчанию возможность использовать %n. Чтобы его включить, используйте
Но если вам все же не хватило магии, загляните в документацию Boost.Format.
24. Параметром шаблона может быть функциональный тип.
25. Массив можно передать по ссылке. Правда, нужно это редко, потому что массивы использовать плохо.
Сишники же пишут
Фу.
Поскольку я слишком долго готовил этот пост, большая часть пунктов в этом разделе уже никого не могут напугать. Но мало ли...
26. Иллюстрируя лямбда-функции, я не смог удержаться, чтобы не привести в качестве примера замечательный экспонат из моей Энциклопедии Юного Диверсанта, выглядящий почти совсем не подозрительно.
Такой код в релизе выводит 8, а если переключить комментарии при fn(5), то 15. Мы захватываем в возвращаемой из operator () лямбде this, указывающий на объект типа B, созданный на стеке функции init. Когда мы выходим из нее, он разрушается, но при правильной фазе Луны после захода в функцию use на том же месте создается объект типа A, который ничем не отличается от B, кроме значения vTable. Ничего не подозревающая лямбда вызывает через сохраненный указатель на this метод A::op.
27. Еще одна солянка из auto type inference, initializer list, uniform initialization, range-based for loops, non-static member initialization и right angle bracket problem.
28. Еще одна большая возможность C++11 это variadic templates, резко упростившие работу со структурами вроде tuple.
29. Простые вещи.
#if 0 это такие вложенные комментарии для бедных; используйте всюду defined вместо #ifdef; генерируйте внятные сообщения об ошибках и предупреждениях.
30. С помощью препроцессора можно получить массу разной информации разной степени полезности.
31. Содержимое макросов часто обрамляется циклом с ложным постусловием
Нужно это для того, чтобы можно было писать вызов макроса, как будто это функция.
Фанаты 1TBS смотрят на любителей опустить фигурного скобца нахмуренно, свирепо и в то же время недоуменно.
32. Все знают, что делает первый макрос, но мало кто слышал про второй. Еще бы, ведь это специфичная возможность VC++.
33. На TopCoder до недавнего перехода на gcc 4.8.1 -std=c++11 популярностью пользовались <?= и >?=. Теперь про них написано в документации, что The G++ minimum and maximum operators (‘<?’ and ‘>?’) and their compound forms (‘<?=’) and ‘>?=’) have been deprecated and are now removed from G++.
Из той же песочницы код
34. GCC поддерживает литералы вида
Откуда 15.5? 0x1.F это 1 + 15/16, а p3 задает экспоненту. Получается 1.11112 × 23 = 1111.12 = 15.5. А бинарные литералы войдут в C++14.
35. Среди адских GCC extensions есть и такой, разрешающий отрезки значений в switch.
36. А еще в GCC есть свои лямбды, с БШ (statement exprs).
37. Вы все еще пишете
include guards? Тогда мы идем к вам!
Поддерживается и GCC, и VC++. Если нет разницы, зачем писать больше?
38. Аналогично и GCC, и VC++ поддерживает упаковку структур, полезную, например, при разборе бинарных форматов.
39. А вот такой очень полезной прагмы в GCC нету.
Возможность указать в одном файле исходников нужную библиотеку и не плодить разные файлы проектов, cmake, autotools и прочего настолько удобна, что гнутые багописцы решили сделать корректность линковки зависящей от порядка перечисления библиотек, только бы ее не имплементить.
40. Существует магическая прагма (работающая как на GCC, так и на VC), которая волшебным образоминогда ускоряет ваш код.
Степень колдунства сильно зависит от вашего знания OpenMP, задачи и количества ядер, а еще от того, не забыли ли вы его включить (в VC Project Properties > C/C++ > Language > Open MP Support, в GCC ключ -fopenmp).
41. Компиляторы предоставляют большое число различных атрибутов (MSVC, GCC), например, выдающих предупреждения при использовании устаревших функций/переменных/классов
VC умеет еще так
Чтобы как-то побороть этот зоопарк, C++11 ввел специальный синтаксис для атрибутов, но пока не включил в требования к реализациям список всего полезного. Выглядеть это будет как-то так
42. В Windows SDK часто встречаются структуры вида
где ANY_SIZE определен как 1. Но и VC, и GCC поддерживают массивы нулевого размера. Такой код
скомпилируется (в VC с предупреждением) и будет работать, пока кто-нибудь не захочет создать объект этой структуры (а не только указатель).
43. Ключевое слово restrict есть и в C++. Пример из википедии:
Оно позволяет сообщить компилятору, что указатели указывают на непересекающиеся области памяти, а значит после изменения памяти по одному указателю ему не нужно будет перечитывать память по другим.
44. А еще на C++ можно писать как на PHP. Вот такой код
прекрасно компилируется и VC, и GCC.
45. В Visual C++ есть настоящие свойства.
В GCC их придется эмулировать.
46.Когда Даже если вдруг все накроется, программы на Visual C++ продолжат работать, потому что они умеют взаимодействовать с SEH!
Если вы не используете какой-нибудь breakpad, всегда полезно оборачивать ваш код в __try и try с выводом какой-нибудь диагностики.
47. А вот так, внученько, мы в Visual C++ итерировались по массивам до этих ваших новомодных auto, range-based for loop и initializer-list.
48. Вообще называть переменные ключевыми словами нельзя, но в Visual C++, если очень хочется, то можно:
Бывает полезно при конфликте имен с Microsoft specific keywords или при взаимодействии с кодом на C#, где template вполне может быть идентификатором.
Иногда исходники на C++ перемешаны с другими, похожими языками.
49. Вот, например, хитрые сишники, у которых нет конструкторов и std::initializer_list понапридумывали кучу разных способов инициализации, например compound literals и designated inits. А [0 ... 5] это и вовсе GCC extension, работает с -std=gnu90.
50. А когда сишники свободны от отлаживания утечек памяти, они завидуют Фортрану. Вот, например, продукт этой зависти, воплощенный в один из GCC extension:
51. Если вы увидите что-то жуткое вида
не пугайтесь, это всего лишь Objective-C, а сразу удаляйте.
52. Аналогично поступайте в случае кода вида
Это C++.NET.
Ну и надо рассказать одной строкой пару страшилок про C++14. Появится yield, литералы вида 2+3i, 7h + 35min и 100'500, функции, принимающие и возвращающие auto, шаблоны переменных. Страустрап пилит паттерн-матчинг круче, чем в Скале. А потом появятся async/await, import, рефлексия времени компиляции и мир окончательно разделится на людей, которые успели выучить C++, пока он еще был простым, и на тех, кто никогда не осилит: первые будут писать весь полезный софт, а вторые клепать сайты-визитки.
Страшилки для других языков.
А какие плюсострашилки знаете вы?
или Как запугать жуниора
В коде на C++ можно увидеть много компиляторо-специфичных или просто редко встречающихся в учебниках штук, смысл которых туманен, а как их гуглить, иногда бывает неясно. К примеру, когда я, будучи еще школьником, переходил с бейсика на C++ и стремительно овладевал непростым понятием цикла for, поля класса и объявления переменных, мне встретился забористый кусок С++-кода с ::, namespace, template< template<> class >, что привело к поломке моего парсера :)
Самые обычные вещи
Сначала разминка
1. В списке объявления переменных астериски и амперсанды относятся к переменной, а не к типу.
int *a, *b, &c = *b;
2. В C++ довольно хитрые области видимости.
const int i = 1;
const int j = 2;
struct x { int x; };
namespace y {
int i[i];
int j = j;
x x;
int y = x.x;
};
const int j = 2;
struct x { int x; };
namespace y {
int i[i];
int j = j;
x x;
int y = x.x;
};
3. Оператор стремления :)
int i = 10; while (i --> 0) cout << i << endl;
На самом деле, конечно, всего-лишь постдекремент и >.
4. В инварианте цикла можно объявлять переменные,
for (int i = 0; int j = 5 - i; ++i) cout << j << endl;
впрочем, как и в условиях.
if (C *c = get_ptr()) { }
5. А в инициализации можно объявлять типы.
for (struct { int a; float b; } i = {0, 1.0}; i.a < 5; ++i.a, i.b *= 3.14) {
cout << i.a << " " << i.b << endl;
}
cout << i.a << " " << i.b << endl;
}
6. Всем известное устройство Даффа.
const int count = 12;
char dest[count], src[count] = "abracadabra", *from = src, *to = dest;
int n = 1 + (count - 1) / 8;
switch (count % 8) {
case 0: do { *to++ = *from++;
case 7: *to++ = *from++;
case 6: *to++ = *from++;
case 5: *to++ = *from++;
case 4: *to++ = *from++;
case 3: *to++ = *from++;
case 2: *to++ = *from++;
case 1: *to++ = *from++;
} while (--n > 0);
}
printf("%s\n", dest);
char dest[count], src[count] = "abracadabra", *from = src, *to = dest;
int n = 1 + (count - 1) / 8;
switch (count % 8) {
case 0: do { *to++ = *from++;
case 7: *to++ = *from++;
case 6: *to++ = *from++;
case 5: *to++ = *from++;
case 4: *to++ = *from++;
case 3: *to++ = *from++;
case 2: *to++ = *from++;
case 1: *to++ = *from++;
} while (--n > 0);
}
printf("%s\n", dest);
Умные слова switch и case не должны скрывать от вас тот факт, что внутре у него goto.
7. В C++ есть несколько способов полностью обнулить структуру.
struct s { int a; float b; } s1, s2 = {0}, s3 = s();
8. Чтобы упростить копипасту в инициализации массивов, в конце может стоять запятая.
int arr[] = {2, 3, 5, 7, 11, };
9. Эта же запятая может стать источником проблем, если она перегружена. Если мы хотим этого избежать, используется такой трюк:
++i, void(), ++j
Например, в таком коде первый цикл исполняется с побочным эффектом:
struct commi
{
int value;
explicit commi(int value = 0) : value(value) { }
commi& operator ++ () { ++value; return *this; }
commi& operator -- () { --value; return *this; }
bool operator < (const commi& rhs) const { return value < rhs.value; }
void operator , (const commi& rhs) const { cout << "called " << value << "," << rhs.value << endl; }
};
ostream& operator << (ostream& out, const commi& rhs) { return out << rhs.value; }
template<typename T>
void func(T arg_i, T arg_j)
{
for (T i = arg_i, j = arg_j; i < j; ++i, --j) {
cout << i << " " << j << endl;
}
for (T i = arg_i, j = arg_j; i < j; ++i, void(), --j) {
cout << i << " " << j << endl;
}
}
func(commi(), commi(5));
{
int value;
explicit commi(int value = 0) : value(value) { }
commi& operator ++ () { ++value; return *this; }
commi& operator -- () { --value; return *this; }
bool operator < (const commi& rhs) const { return value < rhs.value; }
void operator , (const commi& rhs) const { cout << "called " << value << "," << rhs.value << endl; }
};
ostream& operator << (ostream& out, const commi& rhs) { return out << rhs.value; }
template<typename T>
void func(T arg_i, T arg_j)
{
for (T i = arg_i, j = arg_j; i < j; ++i, --j) {
cout << i << " " << j << endl;
}
for (T i = arg_i, j = arg_j; i < j; ++i, void(), --j) {
cout << i << " " << j << endl;
}
}
func(commi(), commi(5));
10. Тернарный оператор правоассоциативен и может выступать в качестве lvalue. Такой код сделает именно то, что должен, присвоит b = 5:
int a = 1, b = 2, c = 3, d = 4;
(a ? b : c) = d == 1 ? 2 : d == 2 ? 3 : d == 3 ? 4 : d == 4 ? 5 : d == 5 ? 6 : -1;
cout << b << c << d << endl;
(a ? b : c) = d == 1 ? 2 : d == 2 ? 3 : d == 3 ? 4 : d == 4 ? 5 : d == 5 ? 6 : -1;
cout << b << c << d << endl;
11. А еще он может возвращать void и кидать исключения (это особенно полезно в списках инициализации классов). Если же в его результатах два разных типа, то берется более общий.
cout << (42 ? 52 : throw logic_error("Why not 42?")) << endl;
42 ? work(42) : work(52);
42 ? void() : work(-1);
cout << typeid(1 ? new Derived : new Base).name() << endl;
42 ? work(42) : work(52);
42 ? void() : work(-1);
cout << typeid(1 ? new Derived : new Base).name() << endl;
12. Программисты на C++ часто забывают о битовых полях. А ведь их размеры могут задаваться аргументами шаблона. И еще у них есть поля-разделители (должны быть безымянными, выравнивают следующее поле по allocation unit boundary).
template<int X, int Y> struct bf {
unsigned char a : X;
unsigned char : 0;
unsigned char b : Y;
};
unsigned char a : X;
unsigned char : 0;
unsigned char b : Y;
};
Я слышал, что у языка C++ есть какой-то безумный предшественник, называемый просто C. Вот, что они используют в ядре Linux вместо static_assert:
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
А еще вот такое.
13. Сначала я думал, что боян и все знают, но оказалось, что не все. Деструктор может быть чисто виртуальным, но при этом иметь тело. Но его нельзя определить сразу же.
struct A { virtual ~A() = 0; };
A::~A() { cout << "~A" << endl; }
struct B : A { ~B() { cout << "~B" << endl; } };
A::~A() { cout << "~A" << endl; }
struct B : A { ~B() { cout << "~B" << endl; } };
14. Многоточие и макросы для взаимодействия с ним:
void func(int n, ...)
{
va_list args;
va_start(args, n);
int value;
for (int i = 0; i < n; ++i) {
value = va_arg(args, int);
cout << value << endl;
}
va_end(args);
}
func(3, 123, 345, 456);
{
va_list args;
va_start(args, n);
int value;
for (int i = 0; i < n; ++i) {
value = va_arg(args, int);
cout << value << endl;
}
va_end(args);
}
func(3, 123, 345, 456);
15. Function-try-block.
struct A {
int a;
A(int a) try : a(a > 0 ? a : throw 42) {
cout << "I'm inside!" << endl;
} catch (...) {
cout << "Oops" << endl;
}
};
int a;
A(int a) try : a(a > 0 ? a : throw 42) {
cout << "I'm inside!" << endl;
} catch (...) {
cout << "Oops" << endl;
}
};
16. Диграфы и триграфы, еще.
// What will the next line do? Increment???????????/
++x;
++x;
??=include <cstdio>
int main()
??<
printf("Hello, world\n");
??>
int main()
??<
printf("Hello, world\n");
??>
int main(){ <:]{%>; } // smile with beard!
В частности, из-за диграфов в Boost всегда пишут < :: вместо <::
17. Булевы операторы можно записать словами.
if (1 not_eq 2 and (0 or 1)) {
cout << "WAT" << endl;
}
cout << "WAT" << endl;
}
Пристально смотрите на слова and, and_eq, not, not_eq, or, or_eq, xor, xor_eq, bitand, bitor, compl.
Visual C++ требует для их работы
#include <iso646.h>
18. Иногда компилятору нужно словами объяснить, что он перед собой видит. Ключевыми словами operator, typename и template.
struct A { template<typename T> T operator () () const { return T(42); } };
struct B { template<typename T> T operator () () const { return T(13); } };
struct C : A, B { };
struct Info {
typedef A base_t;
template<typename T> struct info { typedef int type; };
};
template<typename T> void func()
{
cout << C().T::base_t::template operator()<typename T::template info<void>::type>() << endl;
}
int main()
{
func<Info>();
}
struct B { template<typename T> T operator () () const { return T(13); } };
struct C : A, B { };
struct Info {
typedef A base_t;
template<typename T> struct info { typedef int type; };
};
template<typename T> void func()
{
cout << C().T::base_t::template operator()<typename T::template info<void>::type>() << endl;
}
int main()
{
func<Info>();
}
Кстати, Visual Studio 2013 не переносит тут слова template перед operator, а GCC и Clang наоборот, его отсутствия. ++vc_bugs_discovered_by_me;
19. Некоторые объявления могут казаться немного запутанными. Следует одолеть правило чтения по спирали (или тут).
char *(*(**foo[][8])())[];
Еще можно для расширения кругозора почитать про синтаксис SPECS, предложенный для упрощения подобного кода.
int // integer
int * // pointer to integer
int *[3] // array of 3 pointers to integer
int (*)[3] // pointer to array of 3 integers
int *() // function having no parameters, returning pointer to integer
int (*)(double) // pointer to function of double, returning an integer
//The equivalent SPECS type IDs are:
int // integer
^ int // pointer to integer
[3] ^ int // array of 3 pointers to integer
^ [3] int // pointer to array of 3 integers
(void -> ^int) // function having no parameters, returning pointer to integer
^ (double -> int) // pointer to function taking a double, returning an integer
int * // pointer to integer
int *[3] // array of 3 pointers to integer
int (*)[3] // pointer to array of 3 integers
int *() // function having no parameters, returning pointer to integer
int (*)(double) // pointer to function of double, returning an integer
//The equivalent SPECS type IDs are:
int // integer
^ int // pointer to integer
[3] ^ int // array of 3 pointers to integer
^ [3] int // pointer to array of 3 integers
(void -> ^int) // function having no parameters, returning pointer to integer
^ (double -> int) // pointer to function taking a double, returning an integer
20. Спецификация исключений. Самая, пожалуй, редко используемая возможность C++ (привет, Яндекс! :)), позволяющая указывать, что может выкинуть функция, карая нарушителей std::unexpected и std::terminate. Была беспощадно выпилена в C++11 в пользу более узкого noexcept.
void func1() throw(double) { throw 42.0; } // OK
void func2() noexcept { throw 42; } // not OK
void func2() noexcept { throw 42; } // not OK
21. Насколько извращенным может быть обычный new? В хорошем коде на C++ он почти не встречается вовсе, так что вы вполне могли не видеть чего-то вроде
char buff[sizeof(int) * 4];
cout << *new (buff) int[4]() << endl;
cout << *new (nothrow) int[4]() << endl;
cout << *new (buff) int[4]() << endl;
cout << *new (nothrow) int[4]() << endl;
Первый пример это placement new[] c new-initializer, создающий обнуленный массив из 4 интов на месте буфера buff. Второй обычный new[], но мы просим его не кидать исключений. Об этом и многом другом читайте 5.3.4 [expr.new] :)
22. Думаете, такой код не скомпилируется? Выдаст строку?
cout << 'FOUR' << endl;
На самом деле он выведет 1179604306 (на моей машине). Да, это ROUF, если смотреть побайтово и multicharacter literal, если смотреть в Стандарте. Удобно для всяких магических чисел типа FourCC.
23. А еще среди тяжкого наследия есть такая функция printf.
int a = 3;
float b = 3.14159265358979323f;
int c = 3735929054;
int d = 195948557;
int chars = 0;
printf("%.*f\n", a, b);
printf("%#-20x %#-20x %n %p\n", c, d, &chars, &chars);
printf("%08d\n", chars);
printf("%2$d %1$d", 1, 2);
float b = 3.14159265358979323f;
int c = 3735929054;
int d = 195948557;
int chars = 0;
printf("%.*f\n", a, b);
printf("%#-20x %#-20x %n %p\n", c, d, &chars, &chars);
printf("%08d\n", chars);
printf("%2$d %1$d", 1, 2);
3.142
0xdeadc0de 0xbadf00d 001AFAF4
00000042
2 1
0xdeadc0de 0xbadf00d 001AFAF4
00000042
2 1
Впрочем, последнее не работает нигде, кроме богомерзких юниксов, а заботливая Visual Studio отключает по умолчанию возможность использовать %n. Чтобы его включить, используйте
_set_printf_count_output(1);
Но если вам все же не хватило магии, загляните в документацию Boost.Format.
24. Параметром шаблона может быть функциональный тип.
function<bool(int, int)> fn = less<int>();
25. Массив можно передать по ссылке. Правда, нужно это редко, потому что массивы использовать плохо.
template<typename T, int N>
int array_size(T (&arr)[N]) { return N; }
int arr[42];
cout << array_size(arr) << endl;
int array_size(T (&arr)[N]) { return N; }
int arr[42];
cout << array_size(arr) << endl;
Сишники же пишут
#define array_size(arr) (sizeof(arr) / sizeof(*arr))
Фу.
Темная магия C++11
Поскольку я слишком долго готовил этот пост, большая часть пунктов в этом разделе уже никого не могут напугать. Но мало ли...
26. Иллюстрируя лямбда-функции, я не смог удержаться, чтобы не привести в качестве примера замечательный экспонат из моей Энциклопедии Юного Диверсанта, выглядящий почти совсем не подозрительно.
struct A
{
auto operator () (int x) -> function<int(int)>
{
return [this, x](int y) -> decltype(op(x, y)) {
return op(x, y);
};
}
virtual int op(int x, int y) { return x + y; }
};
struct B : A
{
int op(int x, int y) { return x * y; }
};
void init(function<int(int)>& fn)
{
B b;
cout << &b << endl;
fn = b(3);
//cout << fn(5) << endl;
}
void use(function<int(int)>& fn)
{
A a;
cout << &a << endl;
cout << fn(5) << endl;
}
int main()
{
function<int(int)> fn;
init(fn);
use(fn);
}
{
auto operator () (int x) -> function<int(int)>
{
return [this, x](int y) -> decltype(op(x, y)) {
return op(x, y);
};
}
virtual int op(int x, int y) { return x + y; }
};
struct B : A
{
int op(int x, int y) { return x * y; }
};
void init(function<int(int)>& fn)
{
B b;
cout << &b << endl;
fn = b(3);
//cout << fn(5) << endl;
}
void use(function<int(int)>& fn)
{
A a;
cout << &a << endl;
cout << fn(5) << endl;
}
int main()
{
function<int(int)> fn;
init(fn);
use(fn);
}
Такой код в релизе выводит 8, а если переключить комментарии при fn(5), то 15. Мы захватываем в возвращаемой из operator () лямбде this, указывающий на объект типа B, созданный на стеке функции init. Когда мы выходим из нее, он разрушается, но при правильной фазе Луны после захода в функцию use на том же месте создается объект типа A, который ничем не отличается от B, кроме значения vTable. Ничего не подозревающая лямбда вызывает через сохраненный указатель на this метод A::op.
27. Еще одна солянка из auto type inference, initializer list, uniform initialization, range-based for loops, non-static member initialization и right angle bracket problem.
template<typename T> struct point {
T x{0}, y{0};
point(initializer_list<T> il)
: x{*il.begin()}, y{*(il.begin() + 1)} { }
};
vector<point<int>> points{{1, 2}, {2, 3}, {3, 1}};
for (const auto& pt : points) {
cout << pt.x << " " << pt.y << endl;
}
cout << max({4, 3, 5}) << endl;
for (auto i : {1, 1, 2, 3, 5, 8, 13}) {
cout << i << endl;
}
T x{0}, y{0};
point(initializer_list<T> il)
: x{*il.begin()}, y{*(il.begin() + 1)} { }
};
vector<point<int>> points{{1, 2}, {2, 3}, {3, 1}};
for (const auto& pt : points) {
cout << pt.x << " " << pt.y << endl;
}
cout << max({4, 3, 5}) << endl;
for (auto i : {1, 1, 2, 3, 5, 8, 13}) {
cout << i << endl;
}
28. Еще одна большая возможность C++11 это variadic templates, резко упростившие работу со структурами вроде tuple.
namespace aux {
template<std::size_t...> struct seq { };
template<std::size_t N, std::size_t... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...> { };
template<std::size_t... Is>
struct gen_seq<0, Is...> : seq<Is...> { };
template<class Tuple, std::size_t... Is>
void print_tuple(ostream& os, const Tuple& t, seq<Is...>)
{
auto res = {0, (void(os << (Is == 0 ? "" : ", ") << std::get<Is>(t)), 0)...};
}
} // namespace aux
template<class... Args>
ostream& operator << (ostream& os, const std::tuple<Args...>& t)
{
os << "(";
aux::print_tuple(os, t, aux::gen_seq<sizeof...(Args)>());
return os << ")";
}
cout << make_tuple("abc", 123, 3.14) << endl;
template<std::size_t...> struct seq { };
template<std::size_t N, std::size_t... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...> { };
template<std::size_t... Is>
struct gen_seq<0, Is...> : seq<Is...> { };
template<class Tuple, std::size_t... Is>
void print_tuple(ostream& os, const Tuple& t, seq<Is...>)
{
auto res = {0, (void(os << (Is == 0 ? "" : ", ") << std::get<Is>(t)), 0)...};
}
} // namespace aux
template<class... Args>
ostream& operator << (ostream& os, const std::tuple<Args...>& t)
{
os << "(";
aux::print_tuple(os, t, aux::gen_seq<sizeof...(Args)>());
return os << ")";
}
cout << make_tuple("abc", 123, 3.14) << endl;
Темная магия препроцессора
29. Простые вещи.
#if 0
#if defined(BOOST_MSVC)
#pragma message("warning")
#else
#warning "warning"
#endif
#error "You loose. Again."
#endif
#if defined(BOOST_MSVC)
#pragma message("warning")
#else
#warning "warning"
#endif
#error "You loose. Again."
#endif
#if 0 это такие вложенные комментарии для бедных; используйте всюду defined вместо #ifdef; генерируйте внятные сообщения об ошибках и предупреждениях.
30. С помощью препроцессора можно получить массу разной информации разной степени полезности.
/*
I'm just a function f or void __cdecl f(void) in file Source.cpp at line 29
in code compiled on Nov 29 2013 at 11:07:13 with counter = 0
*/
void f()
{
cout << "I'm just a function " << __FUNCTION__ << " or " <<
#if defined(BOOST_MSVC)
__FUNCSIG__
#else
__func__ << " or " <<
__PRETTY_FUNCTION__
#endif
<< " in file " << __FILE__ << " at line " << __LINE__
<< " in code compiled on " << __DATE__ << " at " << __TIME__
<< " with counter = " << __COUNTER__ << endl;
}
I'm just a function f or void __cdecl f(void) in file Source.cpp at line 29
in code compiled on Nov 29 2013 at 11:07:13 with counter = 0
*/
void f()
{
cout << "I'm just a function " << __FUNCTION__ << " or " <<
#if defined(BOOST_MSVC)
__FUNCSIG__
#else
__func__ << " or " <<
__PRETTY_FUNCTION__
#endif
<< " in file " << __FILE__ << " at line " << __LINE__
<< " in code compiled on " << __DATE__ << " at " << __TIME__
<< " with counter = " << __COUNTER__ << endl;
}
31. Содержимое макросов часто обрамляется циклом с ложным постусловием
#define F12(x) do { f1(x); f2(x); } while (0)
Нужно это для того, чтобы можно было писать вызов макроса, как будто это функция.
if (cond)
F12(x);
else
F12(y);
F12(x);
else
F12(y);
Фанаты 1TBS смотрят на любителей опустить фигурного скобца нахмуренно, свирепо и в то же время недоуменно.
32. Все знают, что делает первый макрос, но мало кто слышал про второй. Еще бы, ведь это специфичная возможность VC++.
#define makestring(x) #x
#define makechar(x) #@x
cout << makestring(My favorite char is) << " " << makechar(w) << endl;
#define makechar(x) #@x
cout << makestring(My favorite char is) << " " << makechar(w) << endl;
Темная магия компиляторов
33. На TopCoder до недавнего перехода на gcc 4.8.1 -std=c++11 популярностью пользовались <?= и >?=. Теперь про них написано в документации, что The G++ minimum and maximum operators (‘<?’ and ‘>?’) and their compound forms (‘<?=’) and ‘>?=’) have been deprecated and are now removed from G++.
Из той же песочницы код
n ?: m; // равносильно n ? n : m
34. GCC поддерживает литералы вида
cout << 0x1.fp3 << endl; // 15.5
cout << 0b00101010 << endl; // 42
cout << 0b00101010 << endl; // 42
Откуда 15.5? 0x1.F это 1 + 15/16, а p3 задает экспоненту. Получается 1.11112 × 23 = 1111.12 = 15.5. А бинарные литералы войдут в C++14.
35. Среди адских GCC extensions есть и такой, разрешающий отрезки значений в switch.
switch ('w') {
case 'a' ... 'z':
printf("lower");
break;
case 'A' ... 'Z':
printf("upper");
break;
}
case 'a' ... 'z':
printf("lower");
break;
case 'A' ... 'Z':
printf("upper");
break;
}
36. А еще в GCC есть свои лямбды, с БШ (statement exprs).
int x = 42;
int y = ({
int res;
if (x % 2 == 0) {
res = x / 2;
} else {
res = 3 * x + 1;
}
res;
});
int y = ({
int res;
if (x % 2 == 0) {
res = x / 2;
} else {
res = 3 * x + 1;
}
res;
});
37. Вы все еще пишете
#if !defined(ONCE_H_SOME_LONG_HASH_JUST_IN_C ASE)
#define ONCE_H_SOME_LONG_HASH_JUST_IN_CASE
// Код
#endif
#define ONCE_H_SOME_LONG_HASH_JUST_IN_CASE
// Код
#endif
include guards? Тогда мы идем к вам!
// once.h
#pragma once
void f() { cout << "Inside!" << endl; }
// main.cpp
#include "once.h"
#include "once.h"
#pragma once
void f() { cout << "Inside!" << endl; }
// main.cpp
#include "once.h"
#include "once.h"
Поддерживается и GCC, и VC++. Если нет разницы, зачем писать больше?
38. Аналогично и GCC, и VC++ поддерживает упаковку структур, полезную, например, при разборе бинарных форматов.
#pragma pack(push, 1)
struct SomeData {
char c;
int i;
double f;
};
#pragma pack(pop)
cout << "size: " << sizeof(SomeData) << endl;
cout << "c offset: " << offsetof(SomeData, c) << endl;
cout << "i offset: " << offsetof(SomeData, i) << endl;
cout << "f offset: " << offsetof(SomeData, f) << endl;
/*
size: 13
c offset: 0
i offset: 1
f offset: 5
*/
struct SomeData {
char c;
int i;
double f;
};
#pragma pack(pop)
cout << "size: " << sizeof(SomeData) << endl;
cout << "c offset: " << offsetof(SomeData, c) << endl;
cout << "i offset: " << offsetof(SomeData, i) << endl;
cout << "f offset: " << offsetof(SomeData, f) << endl;
/*
size: 13
c offset: 0
i offset: 1
f offset: 5
*/
39. А вот такой очень полезной прагмы в GCC нету.
#pragma comment (lib, "opencv_core246.lib")
Возможность указать в одном файле исходников нужную библиотеку и не плодить разные файлы проектов, cmake, autotools и прочего настолько удобна, что гнутые багописцы решили сделать корректность линковки зависящей от порядка перечисления библиотек, только бы ее не имплементить.
40. Существует магическая прагма (работающая как на GCC, так и на VC), которая волшебным образом
vector<long long> sum(10);
#pragma omp parallel for
for (int i = 2; i < 10; ++i) {
for (int j = i; j < 1000000000; j += i) {
sum[i] += j;
}
cout << sum[i] << endl;
}
#pragma omp parallel for
for (int i = 2; i < 10; ++i) {
for (int j = i; j < 1000000000; j += i) {
sum[i] += j;
}
cout << sum[i] << endl;
}
Степень колдунства сильно зависит от вашего знания OpenMP, задачи и количества ядер, а еще от того, не забыли ли вы его включить (в VC Project Properties > C/C++ > Language > Open MP Support, в GCC ключ -fopenmp).
41. Компиляторы предоставляют большое число различных атрибутов (MSVC, GCC), например, выдающих предупреждения при использовании устаревших функций/переменных/классов
#if defined(BOOST_MSVC)
#define DEPRECATED(func, msg) __declspec(deprecated(msg)) func
#else
#define DEPRECATED(func, msg) __attribute__ ((deprecated(msg))) func
#endif
int DEPRECATED(get42, "Use new_get42 instead")() { return 42; }
int new_get42() { return 6 * 7; }
#define DEPRECATED(func, msg) __declspec(deprecated(msg)) func
#else
#define DEPRECATED(func, msg) __attribute__ ((deprecated(msg))) func
#endif
int DEPRECATED(get42, "Use new_get42 instead")() { return 42; }
int new_get42() { return 6 * 7; }
VC умеет еще так
#pragma deprecated(get42)
Чтобы как-то побороть этот зоопарк, C++11 ввел специальный синтаксис для атрибутов, но пока не включил в требования к реализациям список всего полезного. Выглядеть это будет как-то так
int get42() [[deprecated("message")]] { return 42; }
42. В Windows SDK часто встречаются структуры вида
typedef struct _MIB_IFTABLE {
DWORD dwNumEntries;
MIB_IFROW table[ANY_SIZE];
} MIB_IFTABLE, *PMIB_IFTABLE;
DWORD dwNumEntries;
MIB_IFROW table[ANY_SIZE];
} MIB_IFTABLE, *PMIB_IFTABLE;
где ANY_SIZE определен как 1. Но и VC, и GCC поддерживают массивы нулевого размера. Такой код
struct with_zero_arr {
int a;
int b[0];
};
int a;
int b[0];
};
скомпилируется (в VC с предупреждением) и будет работать, пока кто-нибудь не захочет создать объект этой структуры (а не только указатель).
43. Ключевое слово restrict есть и в C++. Пример из википедии:
void add(int *__restrict a, int *__restrict b, int *__restrict c)
{
*a += *c;
*b += *c;
}
{
*a += *c;
*b += *c;
}
Оно позволяет сообщить компилятору, что указатели указывают на непересекающиеся области памяти, а значит после изменения памяти по одному указателю ему не нужно будет перечитывать память по другим.
44. А еще на C++ можно писать как на PHP. Вот такой код
int $x = 123;
прекрасно компилируется и VC, и GCC.
45. В Visual C++ есть настоящие свойства.
class S {
int i;
public:
void setProperty(int value) {
cout << "set" << endl;
i = value;
}
int getProperty() {
cout << "get" << endl;
return i;
}
__declspec(property(get = getProperty, put = setProperty)) int property;
};
S s;
s.property = 5;
cout << s.property << endl;
int i;
public:
void setProperty(int value) {
cout << "set" << endl;
i = value;
}
int getProperty() {
cout << "get" << endl;
return i;
}
__declspec(property(get = getProperty, put = setProperty)) int property;
};
S s;
s.property = 5;
cout << s.property << endl;
В GCC их придется эмулировать.
46.
int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep)
{
if (code == EXCEPTION_ACCESS_VIOLATION) {
cout << "access violation" << endl;
return EXCEPTION_EXECUTE_HANDLER;
} else {
cout << "unknown exception " << code << endl;
return EXCEPTION_CONTINUE_SEARCH;
}
}
int main()
{
int* p = 0x00000000;
__try {
__try {
*p = 13;
/*
access violation
exception
exception handler
*/
} __finally {
puts(AbnormalTermination() ? "exception" : "");
}
} __except (filter(GetExceptionCode(), GetExceptionInformation())) {
cout << "exception handler" << endl;
}
}
{
if (code == EXCEPTION_ACCESS_VIOLATION) {
cout << "access violation" << endl;
return EXCEPTION_EXECUTE_HANDLER;
} else {
cout << "unknown exception " << code << endl;
return EXCEPTION_CONTINUE_SEARCH;
}
}
int main()
{
int* p = 0x00000000;
__try {
__try {
*p = 13;
/*
access violation
exception
exception handler
*/
} __finally {
puts(AbnormalTermination() ? "exception" : "");
}
} __except (filter(GetExceptionCode(), GetExceptionInformation())) {
cout << "exception handler" << endl;
}
}
Если вы не используете какой-нибудь breakpad, всегда полезно оборачивать ваш код в __try и try с выводом какой-нибудь диагностики.
47. А вот так, внученько, мы в Visual C++ итерировались по массивам до этих ваших новомодных auto, range-based for loop и initializer-list.
int vec[] = {1, 1, 2, 3, 5, 8};
for each (int x in vec) {
cout << x << endl;
}
for each (int x in vec) {
cout << x << endl;
}
48. Вообще называть переменные ключевыми словами нельзя, но в Visual C++, если очень хочется, то можно:
int __identifier(template) = 0;
++__identifier(template);
cout << __identifier(template) << endl;
++__identifier(template);
cout << __identifier(template) << endl;
Бывает полезно при конфликте имен с Microsoft specific keywords или при взаимодействии с кодом на C#, где template вполне может быть идентификатором.
Темная магия других языков
Иногда исходники на C++ перемешаны с другими, похожими языками.
49. Вот, например, хитрые сишники, у которых нет конструкторов и std::initializer_list понапридумывали кучу разных способов инициализации, например compound literals и designated inits. А [0 ... 5] это и вовсе GCC extension, работает с -std=gnu90.
struct player { char *name; int rank; } players[] = {
[0].name = "foo",
[0 ... 5].rank = 1, [6 ... 8].rank = 2,
[9] = ((struct player) {"ninth", 9})
};
[0].name = "foo",
[0 ... 5].rank = 1, [6 ... 8].rank = 2,
[9] = ((struct player) {"ninth", 9})
};
50. А когда сишники свободны от отлаживания утечек памяти, они завидуют Фортрану. Вот, например, продукт этой зависти, воплощенный в один из GCC extension:
int n;
scanf("%d", &n);
void *addr[] = {&&one, &&two, &&three};
goto *addr[n];
one:
printf("one\n");
two:
printf("two\n");
three:
printf("three\n");
scanf("%d", &n);
void *addr[] = {&&one, &&two, &&three};
goto *addr[n];
one:
printf("one\n");
two:
printf("two\n");
three:
printf("three\n");
51. Если вы увидите что-то жуткое вида
@interface MyObj : NSObject
+ (id) method1;
- (id) method2;
@end
MyObj *array = [[MyObj funcWith:arg and:arg2] init];
+ (id) method1;
- (id) method2;
@end
MyObj *array = [[MyObj funcWith:arg and:arg2] init];
не пугайтесь, это всего лишь Objective-C, а сразу удаляйте.
52. Аналогично поступайте в случае кода вида
MyObj^ obj = gcnew MyObj;
Это C++.NET.
Ну и надо рассказать одной строкой пару страшилок про C++14. Появится yield, литералы вида 2+3i, 7h + 35min и 100'500, функции, принимающие и возвращающие auto, шаблоны переменных. Страустрап пилит паттерн-матчинг круче, чем в Скале. А потом появятся async/await, import, рефлексия времени компиляции и мир окончательно разделится на людей, которые успели выучить C++, пока он еще был простым, и на тех, кто никогда не осилит: первые будут писать весь полезный софт, а вторые клепать сайты-визитки.
Страшилки для других языков.
А какие плюсострашилки знаете вы?