Неплохая подборка страшилок.
Оригинал взят у
sharpc в
Гримуар C++или N вещей, которые вы могли не видеть в 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;
};
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;
}
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);
Умные слова 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
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;
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;
12. Программисты на C++ часто забывают о битовых полях. А ведь их размеры могут задаваться аргументами шаблона. И еще у них есть поля-разделители (должны быть безымянными, выравнивают следующее поле по allocation unit boundary).
template struct bf {
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; } };
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);
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;
}
};
16. Диграфы и триграфы, еще.
// What will the next line do? Increment???????????/
++x;
??=include
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;
}
Пристально смотрите на слова and, and_eq, not, not_eq, or, or_eq, xor, xor_eq, bitand, bitor, compl.
18. Иногда компилятору нужно словами объяснить, что он перед собой видит. Ключевыми словами operator, typename и template.
struct A { template T operator () () const { return T(42); } };
struct B { template T operator () () const { return T(13); } };
struct C : A, B { };
struct Info {
typedef A base_t;
template struct info { typedef int type; };
};
template void func()
{
cout << C().T::base_t::template operator()::type>() << endl;
}
int main()
{
func();
}
Кстати, 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
20. Спецификация исключений. Самая, пожалуй, редко используемая возможность C++ (привет, Яндекс! :)), позволяющая указывать, что может выкинуть функция, карая нарушителей std::unexpected и std::terminate. Была беспощадно выпилена в C++11 в пользу более узкого noexcept.
void func1() throw(double) { throw 42.0; } // 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;
Первый пример это 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);
3.142
0xdeadc0de 0xbadf00d 001AFAF4
00000042
2 1
Впрочем, последнее не работает нигде, кроме богомерзких юниксов, а заботливая Visual Studio отключает по умолчанию возможность использовать %n. Чтобы его включить, используйте
_set_printf_count_output(1);
Но если вам все же не хватило магии, загляните в документацию Boost.Format.
24. Параметром шаблона может быть функциональный тип.
function fn = less();
25. Массив можно передать по ссылке. Правда, нужно это редко, потому что массивы использовать плохо.
template
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
{
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& fn)
{
B b;
cout << &b << endl;
fn = b(3);
//cout << fn(5) << endl;
}
void use(function& fn)
{
A a;
cout << &a << endl;
cout << fn(5) << endl;
}
int main()
{
function 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 struct point {
T x{0}, y{0};
point(initializer_list il)
: x{*il.begin()}, y{*(il.begin() + 1)} { }
};
vectorint>> 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 {
templatesize_t...> struct seq { };
templatesize_t N, std::size_t... Is>
struct gen_seq : gen_seq1, N-1, Is...> { };
templatesize_t... Is>
struct gen_seq<0, Is...> : seq { };
template
void print_tuple(ostream& os, const Tuple& t, seq)
{
auto res = {0, (void(os << (Is == 0 ? "" : ", ") << std::get(t)), 0)...};
}
} // namespace aux
template
ostream& operator << (ostream& os, const std::tuple& t)
{
os << "(";
aux::print_tuple(os, t, aux::gen_seq());
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 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;
}
31. Содержимое макросов часто обрамляется циклом с ложным постусловием
#define F12(x) do { f1(x); f2(x); } while (0)
Нужно это для того, чтобы можно было писать вызов макроса, как будто это функция.
if (cond)
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;
Темная магия компиляторов
33. На TopCoder до недавнего перехода на gcc 4.8.1 -std=c++11 популярностью пользовались и >?=. Теперь про них написано в документации, что The G++ minimum and maximum operators (‘{C}{C}?’) and their compound forms (‘{C}{C}?=’) 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
Откуда 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;
}
36. А еще в GCC есть свои лямбды, с БШ (statement exprs).
int x = 42;
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_CASE)
#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"
Поддерживается и 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
*/
39. А вот такой очень полезной прагмы в GCC нету.
#pragma comment (lib, "opencv_core246.lib")
Возможность указать в одном файле исходников нужную библиотеку и не плодить разные файлы проектов, cmake, autotools и прочего настолько удобна, что гнутые багописцы решили сделать корректность линковки зависящей от порядка перечисления библиотек, только бы ее не имплементить.
40. Существует магическая прагма (работающая как на GCC, так и на VC), которая волшебным образом иногда ускоряет ваш код.
vector 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;
}
Степень колдунства сильно зависит от вашего знания 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; }
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;
где ANY_SIZE определен как 1. Но и VC, и GCC поддерживают массивы нулевого размера. Такой код
struct with_zero_arr {
int a;
int b[0];
};
скомпилируется (в VC с предупреждением) и будет работать, пока кто-нибудь не захочет создать объект этой структуры (а не только указатель).
43. Ключевое слово restrict есть и в C++. Пример из википедии:
void add(int *__restrict a, int *__restrict b, int *__restrict 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;
В GCC их придется эмулировать.
46. Когда Даже если вдруг все накроется, программы на Visual C++ продолжат работать, потому что они умеют взаимодействовать с SEH!
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;
}
}
Если вы не используете какой-нибудь 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;
}
48. Вообще называть переменные ключевыми словами нельзя, но в Visual C++, если очень хочется, то можно:
int __identifier(template) = 0;
++__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})
};
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");
51. Если вы увидите что-то жуткое вида
@interface MyObj : NSObject
+ (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++, пока он еще был простым, и на тех, кто никогда не осилит: первые будут писать весь полезный софт, а вторые клепать сайты-визитки.
Страшилки для других языков.