Особенностью C++ является возможность создавать безымянные (анонимные) пространства имен, например:
namespace {
int cannotAccessOutsideThisFile() { ... }
} // namespace
Вы можете подумать, что такая функция будет бесполезной - поскольку вы не можете указать имя пространства имен, невозможно получить доступ к чему-либо в нем извне. Но эти безымянные пространства имен являются доступны в файле, в котором они созданы, как если бы для них было неявное предложение using.
У меня вопрос: почему и когда это предпочтительнее статических функций? Или по сути это два способа сделать одно и то же?





Стандарт C++ гласит в разделе 7.3.1.1 «Безымянные пространства имен», параграф 2:
The use of the static keyword is deprecated when declaring objects in a namespace scope, the unnamed-namespace provides a superior alternative.
Статический применяется только к именам объектов, функций и анонимных объединений, но не к объявлениям типов.
Решение об отказе от использования ключевого слова static (влияющего на видимость объявления переменной в единице перевода) было отменено (ссылка). В этом случае использование static или безымянного namespace снова становится по сути двумя способами сделать одно и то же. Для получения дополнительной информации см. Вопрос это SO.
У безымянных namespace есть то преимущество, что вы можете определять локальные типы единиц перевода. Пожалуйста, смотрите вопрос это SO для более подробной информации.
Благодарим Майк Перси за то, что обратил на это мое внимание.
Head Geek спрашивает о статическом ключевом слове, используемом только для функций. Ключевое слово static, применяемое к объекту, объявленному в области пространства имен, определяет его внутреннюю связь. Сущность, объявленная в анонимном пространстве имен, имеет внешнюю связь (C++ / 3.5), однако она гарантированно находится в области с уникальным именем. Эта анонимность безымянного пространства имен эффективно скрывает его объявление, делая его доступным только из единицы перевода. Последнее эффективно работает так же, как ключевое слово static.
в чем недостаток внешней связи? Может ли это повлиять на встраивание?
Те из комитета по дизайну C++, которые сказали, что ключевое слово static устарело, вероятно, никогда не работали с огромным кодом C в большой реальной системе ... (Вы сразу видите ключевое слово static, но не анонимное пространство имен, если оно содержит много объявлений с большим комментарием блоков.)
Поскольку этот ответ появляется в Google как лучший результат для "анонимного пространства имен C++", следует отметить, что использование static больше не является устаревшим. См. stackoverflow.com/questions/4726570/… и open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012 для получения дополнительной информации.
безымянные пространства имен замедляют связывание. использовать статический
@ErikAronesty Звучит неправильно. У вас есть воспроизводимый пример? Начиная с C++ 11 - и даже раньше в некоторых компиляторах - безымянные namespace неявно имеют внутреннюю привязку, поэтому не должно быть никакой разницы. Любые проблемы, которые ранее могли возникнуть из-за неправильной формулировки, были решены, сделав это требованием в C++ 11.
внешняя связь означает, что это не частный символ, а также больше работы для компоновщика
Узнав об этой функции только сейчас, читая ваш вопрос, я могу только строить догадки. Похоже, что это дает несколько преимуществ по сравнению со статической переменной на уровне файла:
Мне было бы интересно узнать, использовал ли кто-нибудь анонимные пространства имен в реальном коде.
Хорошие предположения, но неверные. Область действия этих пространств имен распространяется на весь файл.
Не совсем так, если вы определяете анонимное пространство имен внутри другого пространства имен, оно по-прежнему будет иметь размер только для файла и может рассматриваться только как находящееся в этом пространстве имен. Попробуй это.
Я мог ошибаться, но, думаю, нет, это не для всего файла: оно доступно только для кода после анонимного пространства имен. Это тонкая штука, и обычно я бы не хотел загрязнять источник множеством анонимных пространств имен ... Тем не менее, это может иметь применение.
Использование ключевого слова static для этой цели не рекомендуется стандартом C++ 98. Проблема со статикой в том, что она не применяется к определению типа. Это также перегруженное ключевое слово, используемое по-разному в разных контекстах, поэтому безымянные пространства имен немного упрощают ситуацию.
Если вы хотите использовать тип только в одной единице перевода, объявите его в файле .cpp. В любом случае он не будет доступен из других единиц перевода.
Вы бы подумали, не так ли? Но если другая единица перевода (= cpp-файл) в том же приложении когда-либо объявляет тип с тем же именем, вы столкнетесь с довольно сложными для отладки проблемами :-). Например, вы можете столкнуться с ситуациями, когда vtable для одного из типов используется при вызове методов другого.
Больше не рекомендуется. И определения типов не экспортируются, так что это бессмысленно. статика полезна для автономных функций и глобальных переменных. безымянные пространства имен полезны для классов.
Помещение методов в анонимное пространство имен предотвращает случайное нарушение Одно правило определения, позволяя вам никогда не беспокоиться об именах ваших вспомогательных методов так же, как для некоторых других методов, на которые вы можете ссылаться.
И, как указал Люк, анонимные пространства имен предпочтительнее статических членов по стандарту.
Я имел в виду статические автономные функции (то есть функции с файловой областью), а не статические функции-члены. Статические автономные функции во многом аналогичны функциям в безымянном пространстве имен, поэтому вопрос.
Ах; хорошо, ODR все еще применяется. Отредактировано для удаления абзаца.
как я понял, ODR для статической функции не работает, если он определен в заголовке, и этот заголовок включен более чем в одну единицу перевода, верно? в этом случае вы получаете несколько копий одной и той же функции
@Andy T: Вы действительно не видите "множественных определений" в случае включенного заголовка. Об этом позаботится препроцессор. Если нет необходимости в изучении вывода, сгенерированного препроцессором, что мне кажется довольно экзотическим и редким. Также хорошей практикой является включение «охранников» в файлы заголовков, например: «#ifndef SOME_GUARD - #define SOME_GUARD ...», что должно предотвратить включение препроцессором одного и того же заголовка дважды.
@NikitaVorontsov охранник может предотвратить включение одного и того же заголовка в одну и ту же единицу перевода, однако он позволяет использовать несколько определений в разных единицах перевода. Это может вызвать ошибку компоновщика "несколько определений" в дальнейшем.
Недавно я начал заменять статические ключевые слова анонимными пространствами имен в своем коде, но сразу же столкнулся с проблемой, когда переменные в пространстве имен больше не были доступны для проверки в моем отладчике. Я использовал VC60, поэтому не знаю, не проблема ли это в других отладчиках. Мое обходное решение заключалось в том, чтобы определить пространство имен «модуль», в котором я дал ему имя моего файла cpp.
Например, в моем файле XmlUtil.cpp я определяю пространство имен XmlUtil_I { ... } для всех переменных и функций моего модуля. Таким образом, я могу применить квалификацию XmlUtil_I:: в отладчике для доступа к переменным. В этом случае _I отличает его от общедоступного пространства имен, такого как XmlUtil, которое я, возможно, захочу использовать в другом месте.
Я полагаю, что потенциальный недостаток этого подхода по сравнению с действительно анонимным состоит в том, что кто-то может нарушить желаемую статическую область видимости, используя квалификатор пространства имен в других модулях. Я не знаю, вызывает ли это серьезную озабоченность.
Я тоже это сделал, но с #if DEBUG namespace BlahBlah_private { #else namespace { #endif, поэтому «пространство имен модуля» присутствует только в отладочных сборках, а в противном случае используется истинное анонимное пространство имен. Было бы неплохо, если бы отладчики давали хороший способ справиться с этим. Это тоже смущает Doxygen.
безымянное пространство имен на самом деле не является жизнеспособной заменой static. static означает, что «на самом деле это никогда не связано с TU». безымянное пространство имен означает, что «оно все еще экспортируется как случайное имя, если оно вызывается из родительского класса, находящегося за пределами TU» ...
Есть один крайний случай, когда статика дает удивительный эффект (по крайней мере, для меня). Стандарт C++ 03 утверждает в 14.6.4.2/1:
For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, the candidate functions are found using the usual lookup rules (3.4.1, 3.4.2) except that:
- For the part of the lookup using unqualified name lookup (3.4.1), only function declarations with external linkage from the template definition context are found.
- For the part of the lookup using associated namespaces (3.4.2), only function declarations with external linkage found in either the template definition context or the template instantiation context are found.
...
Приведенный ниже код будет вызывать 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.
Обновление для современного C++
Начиная с C++ '11, члены безымянного пространства имен неявно имеют внутреннюю связь (3.5 / 4):
An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage.
Но в то же время была обновлена версия 14.6.4.2/1, чтобы удалить упоминание о связывании (взято из C++ '14):
For a function call where the postfix-expression is a dependent name, the candidate functions are found using the usual lookup rules (3.4.1, 3.4.2) except that:
For the part of the lookup using unqualified name lookup (3.4.1), only function declarations from the template definition context are found.
For the part of the lookup using associated namespaces (3.4.2), only function declarations found in either the template definition context or the template instantiation context are found.
В результате этого особого различия между статическими и безымянными членами пространства имен больше не существует.
Разве ключевое слово экспорта не должно быть мертвым? Единственные компиляторы, поддерживающие «экспорт», являются экспериментальными, и, если не удивительно, «экспорт» даже не будет реализован в других из-за неожиданных побочных эффектов (помимо того, что этого не было)
См. Статью Херба Саттера о теме: gotw.ca/publications/mill23-x.htm
Интерфейс от Edison Design Group (EDG) совсем не экспериментальный. Это почти наверняка самая стандартная реализация C++ в мире. Компилятор Intel C++ использует EDG.
Какая функция C++ не имеет «неожиданных побочных эффектов»? В случае экспорта это означает, что безымянная функция пространства имен будет найдена из другого TU - это то же самое, как если бы вы включили определение шаблона напрямую. Было бы удивительнее, если бы этого не было!
Думаю, у вас там опечатка - чтобы NS::S работал, разве S не должен находиться внутри namespace {}?
@ Эрик. S объявлен в безымянном пространстве имен, поэтому будет найден NS::S. Если вам интересно, 10.3.1.1 в N4687 описывает семантику для безымянных пространств имен, а 6.4.3.2/2 описывает поиск полных имен.
По опыту я просто отмечу, что, хотя это способ C++ помещать ранее статические функции в анонимное пространство имен, у старых компиляторов иногда могут возникать проблемы с этим. В настоящее время я работаю с несколькими компиляторами для наших целевых платформ, и более современный компилятор Linux отлично справляется с размещением функций в анонимном пространстве имен.
Но более старый компилятор, работающий на Solaris, с которым мы связаны до неопределенного будущего выпуска, иногда принимает его, а иногда помечает как ошибку. Меня беспокоит не ошибка, а то, что делает мощь, когда он принимает. Поэтому, пока мы не перейдем к современным требованиям, мы по-прежнему будем использовать статические (обычно с охватом класса) функции, в которых мы бы предпочли анонимное пространство имен.
Кроме того, если вы используете ключевое слово static для переменной, как в этом примере:
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 и выше будут выдавать предупреждения для функции, на которую нет ссылок, в пространстве имен, а также предупреждение для неиспользуемой статической функции.
Лично я предпочитаю статические функции безымянным пространствам имен по следующим причинам:
Из одного определения функции очевидно и ясно, что она принадлежит той единице перевода, в которой она компилируется. При использовании безымянного пространства имен вам может потребоваться прокрутка и поиск, чтобы увидеть, находится ли функция в пространстве имен.
Некоторые (старые) компиляторы могут рассматривать функции в пространствах имен как внешние. В VS2017 они по-прежнему внешние. По этой причине, даже если функция находится в безымянном пространстве имен, вы все равно можете пометить их как статические.
Статические функции очень похожи на C или C++, тогда как безымянные пространства имен, очевидно, относятся только к C++. безымянные пространства имен также добавляют дополнительный уровень в отступы, и мне это не нравится :)
Итак, я рад видеть такое использование static для функций больше не рекомендуется.
Предполагается, что функции в анонимных пространствах имен имеют внешнюю связь. Их просто искалечили, чтобы сделать их уникальными. Только ключевое слово static фактически применяет локальную связь к функции. Кроме того, неужели только бредовый сумасшедший действительно добавит отступы для пространств имен?
Разница заключается в названии искаженного идентификатора (_ZN12_GLOBAL__N_11bE против _ZL1b, что на самом деле не имеет значения, но оба они собраны в локальные символы в таблице символов (отсутствие директивы asm .global).
#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
Все анонимные пространства имен 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
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
//inline has the same output
Вы также можете иметь анонимные встроенные пространства имен, но, насколько я могу судить, inline в анонимном пространстве имен имеет нулевой эффект
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++ 11 использование
staticв этом контексте было нерекомендованный; хотя безымянное пространство имен - превосходная альтернативаstatic, есть случаи, когда он терпит неудачу, когда на помощь приходитstatic.