Суть состоит в следующем. Например, большинство программистов для определения количества элементов в массиве используют следующую конструкцию:
int XX[] = { 1, 2, 3, 4 }; size_t N = sizeof(XX) / sizeof(XX[0]);Обычно это оформляют в макрос следующего вида:
#define count_of(arg) (sizeof(arg) / sizeof(arg[0]))
Однако он может послужить причиной ошибки, так как ему случайно можно подсунуть простой указатель, и он не будет возражать по этому поводу:void Test(int C[3]) { int A[3]; int *B = Foo(); size_t x = count_of( A ); // Ok x = count_of( B ); // Error x = count_of( C ); // Error }Конструкция count_of(A) работает корректно и возвращает количество элементов в массиве A, равное трём. Но если случайно применить count_of() к указателю, то результатом будет малоосмысленное значение.
В проекте Chromium гугловские программисты использовали такой макрос, который позволяет избежать описанных выше ошибок. Вот этот магический макрос:
template <typename T, size_t N> char (&ArraySizeHelper(T (&array)[N]))[N]; #define arraysize(array) (sizeof(ArraySizeHelper(array)))Объяснено на хабре было плохо (ну для меня по крайней мере), потом нашел другое объяснение, где уже было немного понятнее.
К моему позору, я начисто забыл, как в С++ выглядит прототип функции, возвращающей ссылку на массив. А выглядит он, например, так:
int (&get_array())[10];Это прототип функции, возвращающей ссылку на массив из 10 интов. Такой способ определения ссылки на массив из N элементов - это наследие C. Выглядит точно так же, как и определение указателя на массив, достаточно "&" заменить на "*".
Далее все очень просто:
template <typename T, size_t N> char (&ArraySizeHelper(T (&array)[N]))[N];- это шаблонизированный прототип функции, принимающей ссылку на массив произвольного типа T из N элементов и возвращающий ссылку на массив типа char из N элементов. Очевидно, что char потому, что число элементов в массиве в этом случае равно количеству байт, занимаемым массовом. Смысл шаблона функции именно в том, что размер массива "магически" передается сразу на выход функции, фактически определяя тип возвращаемого значения (а конкретно размер) во время компиляции. То есть, фактически, саму функцию определять не надо - достаточно только ее прототипа. А он будет сгененерирован во время компиляции. То, что функция реально не вызывается, вызвало у меня некое недопонимание. Однако именно так дело и обстоит. Такова особенность работы функции sizeof() - ей достаточно знать только тип вычисляемого выражения. А он известен на этапе компиляции. То есть нет необходимости вызывать саму функцию. А значит, нет необходимости и ее реализовывать - достаточно только объявления. Почитав немного Александреску, такие фокусы воспринимать уже гораздо легче и привычнее.Чтобы было понятнее, лучше всего привести пару примеров. У нас определен такой массив:
const int arr_size = 10;
const int int_arr[arr_size];
Теперь, если раскрыть шаблон ArraySizeHelper для него, то мы получим такое определение функции:char (&ArraySizeHelper(int (&array)[10]))[10];
То есть прототип функции, получающей параметром ссылку на массив из 10 целых чисел и возвращающую ссылку на массив из 10 char. Естественно, что sizeof() для массива из 10 элементов типа char вернет 10.
Теперь, при попытке подсунуть что-то не то будет ошибка компиляции, что позволит выявить ошибку:template <typename T, size_t N> char (&ArraySizeHelper(T (&array)[N]))[N]; #define arraysize(array) (sizeof(ArraySizeHelper(array))) void Test(int C[3]) { int A[3]; int *B = Foo(); size_t x = arraysize( A ); // Ok x = arraysize( B ); // Ошибка компиляции x = arraysize( C ); // Ошибка компиляции }
P.S.
Для подсветки синтаксиса использовал highlight.hohli.com
Комментариев нет:
Отправить комментарий