Pull to refresh
1036.07
OTUS
Цифровые навыки от ведущих экспертов

Анонимные пространства имен vs. static в пространстве имен

Reading time6 min
Views6.7K
Original author: Pranay Kumar

Содержание

  1. Единица трансляции

  2. Анонимные пространства имен

  3. Static и static в пространстве имен

  4. Примеры

  5. Где хороши анонимные пространства имен

  6. Где целесообразнее статические пространства имен 

  7. Заключение

Единица трансляции

В соответствии со стандартом С++ (ссылка на 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 может использоваться для объявления переменных и функций в:

  1. Глобальной области видимости (global scope) — переменные и функции

  2. Области видимости пространства имен (namespace scope) — переменные и функции

  3. Области видимости класса (class scope) — переменные и функции

  4. Локальной области видимости (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.

  1. Статические члены-данные/функции-члены/анонимные классы/именованные классы/перечисление класса в области видимости пространства имен имеют тот же тип связи, что и заключающий их класс.

  2. Тип связи шаблона класса будет такой же, что и у самого внутреннего заключающего его класса или пространства имен, в котором он объявлен.

  3. Специализации шаблона (явные или неявные), имеющего внутреннюю связь, отличаются от всех остальных специализаций в других единицах трансляции.

Примеры

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".

Tags:
Hubs:
Total votes 14: ↑12 and ↓2+13
Comments2

Articles

Information

Website
otus.ru
Registered
Founded
Employees
101–200 employees
Location
Россия
Representative
OTUS