Безымянное / анонимное пространство имен против статических функций
Особенностью C++ является возможность создавать безымянные (анонимные) пространства имен, например:
namespace {
int cannotAccessOutsideThisFile() { ... }
} // namespace
Вы могли бы подумать, что такая функция будет бесполезна - поскольку вы не можете указать имя пространства имен, невозможно получить к нему доступ извне. Но эти безымянные пространства имен доступны внутри файла, в котором они созданы, как если бы у вас было неявное условие использования для них.
Мой вопрос: почему или когда это было бы предпочтительнее использования статических функций? Или это по сути два способа сделать одно и то же?
10 ответов
Стандарт C++ гласит в разделе 7.3.1.1 Безымянные пространства имен, параграф 2:
Использование статического ключевого слова не рекомендуется при объявлении объектов в области пространства имен. Пространство без имени обеспечивает превосходную альтернативу.
Статика применяется только к именам объектов, функций и анонимных объединений, но не к объявлениям типов.
Редактировать:
Решение об отказе от использования этого ключевого слова static (влияет на видимость объявления переменной в модуле перевода) было отменено ( ссылка). В этом случае использование статического или безымянного пространства имен вернулось к тому, чтобы быть по сути двумя способами сделать одно и то же. Для дальнейшего обсуждения, пожалуйста, смотрите этот вопрос.
Неназванные пространства имен по-прежнему имеют то преимущество, что позволяют определять локальные типы перевода. Пожалуйста, смотрите этот вопрос для более подробной информации.
Благодарю Mike Percy за то, что он привлек мое внимание.
Помещение методов в анонимное пространство имен предотвращает случайное нарушение правила One Definition, позволяя вам никогда не беспокоиться о присвоении имен вашим вспомогательным методам так же, как и другим методам, на которые вы можете ссылаться.
И, как указал luke, анонимные пространства имен предпочитаются стандартом над статическими членами.
Есть один крайний случай, когда static имеет удивительный эффект (по крайней мере, для меня). Стандарт C++03 в 14.6.4.2/1 гласит:
Для вызова функции, который зависит от параметра шаблона, если имя функции является неквалифицированным идентификатором, но не идентификатором шаблона, функции-кандидаты находятся с использованием обычных правил поиска (3.4.1, 3.4.2), за исключением того, что:
- Для части поиска с использованием поиска без определения имени (3.4.1) найдены только объявления функций с внешней связью из контекста определения шаблона.
- Для части поиска, использующей связанные пространства имен (3.4.2), обнаруживаются только объявления функций с внешней связью, найденные либо в контексте определения шаблона, либо в контексте создания шаблона.
...
Ниже код позвонит foo(void*)
и не foo(S const &)
как и следовало ожидать.
template <typename T>
int b1 (T const & t)
{
foo(t);
}
namespace NS
{
namespace
{
struct S
{
public:
operator void * () const;
};
void foo (void*);
static void foo (S const &); // Not considered 14.6.4.2(b1)
}
}
void b2()
{
NS::S s;
b1 (s);
}
Само по себе это, вероятно, не так уж и важно, но оно подчеркивает это для полностью совместимого компилятора C++ (то есть с поддержкой export
) static
Ключевое слово по-прежнему будет иметь функциональность, которая недоступна другим способом.
// bar.h
export template <typename T>
int b1 (T const & t);
// bar.cc
#include "bar.h"
template <typename T>
int b1 (T const & t)
{
foo(t);
}
// foo.cc
#include "bar.h"
namespace NS
{
namespace
{
struct S
{
};
void foo (S const & s); // Will be found by different TU 'bar.cc'
}
}
void b2()
{
NS::S s;
b1 (s);
}
Единственный способ гарантировать, что функция в нашем безымянном пространстве имен не будет найдена в шаблонах, использующих ADL, состоит в том, чтобы сделать это static
,
Обновление для Modern C++
Начиная с C++ '11, члены безымянного пространства имен неявно имеют внутреннюю связь (3.5/4):
Неназванное пространство имен или пространство имен, объявленное прямо или косвенно в безымянном пространстве имен, имеет внутреннюю связь.
Но в то же время 14.6.4.2/1 был обновлен, чтобы убрать упоминание о связи (это взято из C++ '14):
Для вызова функции, где postfix-выражение является зависимым именем, функции-кандидаты находятся с использованием обычных правил поиска (3.4.1, 3.4.2), за исключением того, что:
Для части поиска с использованием поиска без определения имени (3.4.1) найдены только объявления функций из контекста определения шаблона.
Для части поиска с использованием связанных пространств имен (3.4.2) обнаруживаются только объявления функций, найденные либо в контексте определения шаблона, либо в контексте создания шаблона.
В результате этого конкретного различия между статическими и неназванными членами пространства имен больше не существует.
Лично я предпочитаю статические функции над безымянными пространствами имен по следующим причинам:
из определения функции ясно и ясно, что он является частным для модуля перевода, где он компилируется. С безымянным пространством имен вам, возможно, придется прокручивать и искать, чтобы увидеть, находится ли функция в пространстве имен.
функции в пространствах имен могут рассматриваться некоторыми внешними компиляторами как внешние. В VS2017 они все еще внешние. По этой причине, даже если функция находится в безымянном пространстве имен, вы все равно можете пометить их как статические.
статические функции ведут себя очень похоже в C или C++, тогда как безымянные пространства имен, очевидно, являются только C++. безымянные пространства имен также добавляют дополнительный уровень, если отступ, и мне это не нравится:)
Итак, я рад видеть, что использование static для функций больше не считается устаревшим.
Недавно я начал заменять статические ключевые слова анонимными пространствами имен в своем коде, но сразу столкнулся с проблемой, когда переменные в пространстве имен больше не были доступны для проверки в моем отладчике. Я использовал VC60, поэтому я не знаю, не является ли это проблемой с другими отладчиками. Мой обходной путь состоял в том, чтобы определить пространство имен 'module', где я дал ему имя моего файла cpp.
Например, в моем файле XmlUtil.cpp я определяю пространство имен XmlUtil_I { ... } для всех переменных и функций моего модуля. Таким образом, я могу применить XmlUtil_I:: qualification в отладчике для доступа к переменным. В этом случае _I отличает его от публичного пространства имен, такого как XmlUtil, которое я, возможно, захочу использовать в другом месте.
Я предполагаю, что потенциальный недостаток этого подхода по сравнению с по-настоящему анонимным заключается в том, что кто-то может нарушить желаемую статическую область, используя квалификатор пространства имен в других модулях. Я не знаю, является ли это серьезной проблемой, хотя.
Разница в названии искаженного идентификатора (_ZN12_GLOBAL__N_11bE
против _ZL1b
, что на самом деле не имеет значения, но оба они собраны в локальные символы в таблице символов (отсутствие .global
asm).
#include<iostream>
namespace {
int a = 3;
}
static int b = 4;
int c = 5;
int main (){
std::cout << a << b << c;
}
.data
.align 4
.type _ZN12_GLOBAL__N_11aE, @object
.size _ZN12_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_11aE:
.long 3
.align 4
.type _ZL1b, @object
.size _ZL1b, 4
_ZL1b:
.long 4
.globl c
.align 4
.type c, @object
.size c, 4
c:
.long 5
.text
Что касается вложенного анонимного пространства имен:
namespace {
namespace {
int a = 3;
}
}
.data
.align 4
.type _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, @object
.size _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_112_GLOBAL__N_11aE:
.long 3
.align 4
.type _ZL1b, @object
.size _ZL1b, 4
Все анонимные пространства имен 1-го уровня в единице перевода объединяются друг с другом. Все вложенные анонимные пространства имен 2-го уровня в единице перевода объединяются друг с другом.
Вы также можете иметь вложенное (встроенное) пространство имен в анонимном пространстве имен
namespace {
namespace A {
int a = 3;
}
}
.data
.align 4
.type _ZN12_GLOBAL__N_11A1aE, @object
.size _ZN12_GLOBAL__N_11A1aE, 4
_ZN12_GLOBAL__N_11A1aE:
.long 3
.align 4
.type _ZL1b, @object
.size _ZL1b, 4
which for the record demangles as:
.data
.align 4
.type (anonymous namespace)::A::a, @object
.size (anonymous namespace)::A::a, 4
(anonymous namespace)::A::a:
.long 3
.align 4
.type b, @object
.size b, 4
Вы также можете иметь анонимные встроенные пространства имен, но, насколько я могу судить, inline
в анонимном пространстве имен имеет 0 эффекта
inline namespace {
inline namespace {
int a = 3;
}
}
_ZL1b
: _Z
означает, что это искаженный идентификатор. L
означает, что это местный символ через static
. 1
это длина идентификатора b
а затем идентификатор b
_ZN12_GLOBAL__N_11aE
_Z
означает, что это искаженный идентификатор. N
означает, что это пространство имен 12
это длина имени анонимного пространства имен _GLOBAL__N_1
, затем имя анонимного пространства имен _GLOBAL__N_1
, тогда 1
это длина идентификатора a
, a
это идентификатор a
а также E
закрывает идентификатор, который находится в пространстве имен.
_ZN12_GLOBAL__N_11A1aE
то же самое, что и выше, за исключением того, что в нем есть другой уровень пространства имен 1A
Из опыта я просто отмечу, что, хотя это способ C++ поместить ранее статические функции в анонимное пространство имен, старые компиляторы могут иногда иметь проблемы с этим. В настоящее время я работаю с несколькими компиляторами для наших целевых платформ, и более современный компилятор Linux хорошо размещает функции в анонимном пространстве имен.
Но более старый компилятор, работающий на Solaris, который мы используем до неуказанного будущего выпуска, иногда принимает его, а иногда отмечает его как ошибку. Ошибка не в том, что меня беспокоит, а в том, что он может делать, когда принимает это. Таким образом, пока мы не станем современными по всем направлениям, мы все еще будем использовать статические (обычно классовые) функции, где мы предпочитаем анонимное пространство имен.
Использование статического ключевого слова для этой цели не рекомендуется стандартом C++98. Проблема со статическим в том, что он не относится к определению типа. Это также перегруженное ключевое слово, используемое по-разному в разных контекстах, поэтому безымянные пространства имен немного упрощают вещи.
Кроме того, если кто-то использует статическое ключевое слово для переменной, как в этом примере:
namespace {
static int flag;
}
Это не будет видно в файле сопоставления
Отличие компилятора между анонимными пространствами имен и статическими функциями можно увидеть при компиляции следующего кода.
#include <iostream>
namespace
{
void unreferenced()
{
std::cout << "Unreferenced";
}
void referenced()
{
std::cout << "Referenced";
}
}
static void static_unreferenced()
{
std::cout << "Unreferenced";
}
static void static_referenced()
{
std::cout << "Referenced";
}
int main()
{
referenced();
static_referenced();
return 0;
}
Компиляция этого кода с VS 2017 (указание флага предупреждения уровня 4 /W4 для включения предупреждения C4505: удаленная локальная функция была удалена) и gcc 4.9 с флагом -Wunused-function или -Wall показывает, что VS 2017 будет выдавать только предупреждение для неиспользуемая статическая функция. gcc 4.9 и выше, а также clang 3.3 и выше, будут генерировать предупреждения для функции без ссылок в пространстве имен, а также предупреждения о неиспользуемой статической функции.
Узнав об этой функции только сейчас, читая ваш вопрос, я могу только строить догадки. Похоже, это обеспечивает несколько преимуществ по сравнению со статической переменной на уровне файлов:
- Анонимные пространства имен могут быть вложены друг в друга, обеспечивая несколько уровней защиты, из которых символы не могут выйти.
- Несколько анонимных пространств имен могут быть помещены в один и тот же исходный файл, создавая фактически разные области статического уровня в одном и том же файле.
Мне было бы интересно узнать, использовал ли кто-нибудь анонимные пространства имен в реальном коде.