Содержание
Единица трансляции
Анонимные пространства имен
Static и static в пространстве имен
Примеры
Где хороши анонимные пространства имен
Где целесообразнее статические пространства имен
Заключение
Единица трансляции
В соответствии со стандартом С++ (ссылка на WaybackMachine):
Единица трансляции — это базовая единица компиляции в C++. Она состоит из всего содержимого одного файла с исходным кодом плюс содержимое любых заголовочных файлов, прямо или косвенно включенных в него, минус строки кода, которые были проигнорированы с помощью условных операторов препроцессора.
Анонимные пространства имен
Одной из фич C++ является возможность создавать анонимные (безымянные) пространства имен, например:
namespace {
int invisible_to_others_translation_unit() { ... }
} // пространство имен
Эти анонимные пространства имен доступны в единице трансляции, в которой они определены, как если бы у вас был неявный using-спецификатор для них. unnamed-namespace-definition ведет себя подобно следующему коду:
inline namespace unique { /* пустое тело / }
using namespace unique ;
namespace unique { namespace-body }
Члены встроенного пространства имен ведут себя так, как если бы они были членами родительского пространства имен.
Static и namespace-static
Ключевое слово static
может использоваться для объявления переменных и функций в:
Глобальной области видимости (global scope) — переменные и функции
Области видимости пространства имен (namespace scope) — переменные и функции
Области видимости класса (class scope) — переменные и функции
Локальной области видимости (local scope) — только переменные
Статическое время жизни означает, что объект (или переменная) будет создан при запуске программы и уничтожен по завершению программы.
Связь — это видимость символов для компоновщика при обработке файлов.
Внешняя связь (external linkage) присуща вещам, которые должны существовать за пределами конкретной единицы трансляции, другими словами, доступным в рамках всей программы.
Внутренняя связь (internal linkage) присуща всему, что находится исключительно в рамках одной конкретной единицы трансляции.
По умолчанию объект или переменная, определенные в глобальном пространстве имен, имеют статическое время жизни и внешнюю связь. Однако, когда вы объявляете переменную или функцию в области видимости файла (глобальной и/или области видимости пространства имен), ключевое слово static указывает на то, что переменная или функция имеет внутреннюю связь.
//в глобальной или области видимости пространства имен
int i; // extern по умолчанию
const int ci; // static по умолчанию
extern const int eci; // явно extern
static int si; // явно static
// то же самое и с функциями (но глобальных константных функций нет)
int foo(); // extern по умолчанию
static int bar(); // явно static
const
переменные по умолчанию обладают внутренней связью, если только они не объявлены как extern
.
Статические члены-данные/функции-члены/анонимные классы/именованные классы/перечисление класса в области видимости пространства имен имеют тот же тип связи, что и заключающий их класс.
Тип связи шаблона класса будет такой же, что и у самого внутреннего заключающего его класса или пространства имен, в котором он объявлен.
Специализации шаблона (явные или неявные), имеющего внутреннюю связь, отличаются от всех остальных специализаций в других единицах трансляции.
Примеры
namespace test{
static int i = 5; // internal linkage, definition is here// внутренняя связь, определение прямо здесь
// И никакого повторного определения!
}
Если вы удалите static
, у вас скорее всего возникнут ошибки повторного определения.
vs
namespace {
int i = 5; // внутренняя связь по умолчанию, недостижимо из других
// единиц трансляции.
class invisible_to_others { };
}
Где хороши анонимные пространства имен
1. Пространства имен работают со всем
Анонимное пространство имен превосходит ключевое слово static
в первую очередь из-за того, что ключевое слово static
применимо только к объявлениям переменных и функции, но не к определяемымпользователем типам.
Следующий код допустим в C++:
//допустимый код
static int sample_function() { / тело функции / }
static int sample_variable;
А этот код НЕ допустим:
// недопустимый код
static class sample_class { / тело класса / };
static struct sample_struct { / тело структуры / };
Таким образом, очевидное решение заключается в использовании анонимного пространства имен, а именно:
//допустимый код
namespace
{
class sample_class { / тело класса / };
struct sample_struct { / тело структуры */ };
}
2. У static слишком много значений
a) namespace-static — внутренняя связь
b) local-static — продление жизни локальной переменной
c) member-static — метод класса
3. Однородность и согласованность
Пространства имен обеспечивают единообразный и непротиворечивый способ контроля видимости в глобальной области видимости. Вам не нужно использовать разные инструменты для достижения одной и той же цели.
4. Аргументы шаблона
C++ не позволяет использовать типы и указатели/ссылки на объекты или функции с внутренней связью (статические) в качестве параметров шаблона.
Где целесообразнее статические пространства имен
1. Специализация шаблонов
Анонимные пространства имен не могут специализировать шаблоны за пределами namespace-блока
Ключевое слово static
заставляет каждую единицу трансляции, которая включает в себя заголовок интерфейса, содержащий объявление пространства имен, иметь внутреннюю копию статической переменной/функции (фактически резервирует место). Следовательно, мы не получаем ошибки повторного определения. То же самое верно и для const
без предшествующего ему extern
, который определяет переменную со внутренней связью.
2. Лаконичная глобальная таблица символов
static
предотвращает попадание имени в глобальную таблицу символов. В узком смысле это оптимизация, но очень важная с практической точки зрения. Имена в анонимных пространствах имен этим похвастаться не могут.
Однако функции в анонимных пространствах имен, как правило, добавляются в таблицу символов, но помечаются как internal или mangled, так что они фактически недоступны.
Таким образом, в общем случае программа, использующая static
для своих translation-unit-local namespace-level
функций, генерирует меньше работы для компоновщика и потенциально будет выполняться быстрее, чем эквивалентная программа, использующая анонимные пространства имен.
Заключение
Если вы работаете с заголовками, вам нужно избегать решений, предполагающих анонимные пространства имен. Из-за внутренней связи по умолчанию каждая единица трансляции будет определять свой собственный уникальный экземпляр членов анонимного пространства имен, что может привести к неожиданным результатам, раздуванию итогового исполняемого файла или непреднамеренному вызову неопределенного поведения из-за нарушений правила одного определения (ODR).
Утверждение, что переменные, определенные в анонимных пространствах имен, неявно являются статическими, в корне неверно, и static
все же не бесполезен в пространствах имен. В общем, об этом хорошо говорит следующий гайдлайн:
Предпочитайте обычные функции и типы в анонимном пространстве имен (если только они не в заголовочных файлах, тогда используйте
static
).Если необходимо специализировать методы шаблонов классов, или же оптимизация компоновщика является серьезной проблемой,
static
— это хороший вариант.
Анонимные пространства имен предназначены для защиты локальности, а не для предоставления интерфейса.
Бонусный вопрос 1
namespace { class A { } ; }
void foo ( A ) // _Z3fooN12_GLOBAL__N_11AE
{ ; }
Символ функции, по-видимому, будет ссылаться на имя A, которое является членом пространства имен с уникальным именем.
Каков будет тип связи foo
?
Ответ
В то время как имя foo технически имеет внешнюю связь, на него не могут ссылаться другие единицы трансляции, так как записать название типа параметр foo не возможно.
Ссылки
Связь функции с параметром из безымянного пространства имен.
DCL59-CPP. Не определяйте анонимное пространство имен в заголовочном файле.
Материал подготовлен в рамках онлайн-курса "C++ Developer. Professional".