Безымянное / анонимное пространство имен против статических функций

508

Особенностью C ++ является возможность создавать безымянные (анонимные) пространства имен, например:

namespace {
    int cannotAccessOutsideThisFile() { ... }
} // namespace

Можно подумать, что такая функция будет бесполезной - поскольку вы не можете указать имя пространства имен, невозможно получить к нему доступ извне. Но эти безымянные пространства имен являются доступными в файле они создали в, как если бы вы имели неявное с помощью придаточного к ним.

Мой вопрос: почему или когда это было бы предпочтительнее использования статических функций? Или это по сути два способа сделать одно и то же?

Главный Компьютерщик
источник

Ответы:

332

Стандарт C ++ гласит в разделе 7.3.1.1 Безымянные пространства имен, параграф 2:

Использование статического ключевого слова не рекомендуется при объявлении объектов в области пространства имен. Пространство без имени обеспечивает превосходную альтернативу.

Статика применяется только к именам объектов, функций и анонимных объединений, но не к объявлениям типов.

Редактировать:

Решение об отказе от использования этого ключевого слова static (влияет на видимость объявления переменной в модуле перевода) было отменено ( ссылка ). В этом случае использование статического или безымянного пространства имен вернулось к тому, чтобы быть по сути двумя способами сделать одно и то же. Для дальнейшего обсуждения, пожалуйста, смотрите этот вопрос.

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

Кредит идет Майку Перси за то, что он обратил на это мое внимание.

Люк
источник
39
Head Geek спрашивает, какое статическое ключевое слово используется только для функций. Ключевое слово static, примененное к объекту, объявленному в области пространства имен, определяет его внутреннюю связь. Объект, объявленный в анонимном пространстве имен, имеет внешнюю связь (C ++ / 3.5), однако он гарантированно живет в области с уникальным именем. Эта анонимность безымянного пространства имен фактически скрывает его декларацию, делая его доступным только из модуля перевода. Последний эффективно работает так же, как статическое ключевое слово.
Mloskot
5
В чем недостаток внешней связи? Может ли это повлиять на встраивание?
Алекс
17
Те в C ++, кто сказал, что статическое ключевое слово устарело, вероятно, никогда не работали с огромным кодом C в большой реальной системе ... (Вы сразу увидите статическое ключевое слово, но не анонимное пространство имен, если оно содержит много объявлений с большим комментарием блоки.)
Кальмарий
23
Поскольку этот ответ встречается в Google как лучший результат для «анонимного пространства имен c ++», следует отметить, что использование static больше не считается устаревшим. См stackoverflow.com/questions/4726570/... и open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012 для получения дополнительной информации.
Майкл Перси
2
@ErikAronesty Это звучит неправильно. У вас есть воспроизводимый пример? Начиная с C ++ 11 - и даже до этого в некоторых компиляторах - безымянные namespaces неявно имеют внутреннюю связь, поэтому не должно быть никакой разницы. Любые проблемы, которые ранее могли возникнуть из-за неправильной формулировки, были решены путем введения этого требования в C ++ 11.
underscore_d
73

Помещение методов в анонимное пространство имен предотвращает случайное нарушение правила One Definition , позволяя вам никогда не беспокоиться о присвоении имен вашим вспомогательным методам так же, как и другим методам, на которые вы можете ссылаться.

И, как указал luke, анонимные пространства имен предпочитаются стандартом над статическими членами.

hazzen
источник
2
Я имел в виду статические автономные функции (то есть функции с файловой областью), а не статические функции-члены. Статические автономные функции во многом аналогичны функциям в безымянном пространстве имен, поэтому возникает вопрос.
Глава Geek
2
Ах; хорошо, ODR все еще применяется. Отредактировано для удаления абзаца.
hazzen
как я понял, ODR для статической функции не работает, когда она определена в заголовке, и этот заголовок включен в несколько единиц перевода, верно? в этом случае вы получаете несколько копий одной и той же функции
Андрей Тылычко
@ Энди Т: Вы действительно не видите «множественные определения» в случае включенного заголовка. Препроцессор позаботится об этом. Если нет необходимости изучать результаты, которые генерирует препроцессор, что для меня выглядит довольно экзотично и редко. Также рекомендуется включать «охранники» в заголовочные файлы, например: «#ifndef SOME_GUARD - #define SOME_GUARD ...», что должно препятствовать тому, чтобы препроцессор дважды включал один и тот же заголовок.
Никита Воронцов
@NikitaVorontsov охранник может запретить включение одного и того же заголовка в одну и ту же единицу перевода, однако он допускает несколько определений в разных единицах перевода. Это может привести к ошибке компоновщика с несколькими определениями.
Алекс
37

Есть один крайний случай, когда 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), найдены только объявления функций, найденные либо в контексте определения шаблона, либо в контексте создания экземпляра шаблона.

В результате этого конкретного различия между статическими и неназванными членами пространства имен больше не существует.

Ричард Корден
источник
3
Разве ключевое слово экспорта не должно быть мертвым? Единственные компиляторы, поддерживающие «экспорт», являются экспериментальными, и, если не будет неожиданностей, «экспорт» даже не будет реализован в других из-за неожиданных побочных эффектов (в дополнение к тому, чего не было, ожидалось)
paercebal
2
См. Статью Херба Саттера на эту тему: gotw.ca/publications/mill23-x.htm
paercebal
3
Интерфейс от Edison Design Group (EDG) совсем не экспериментальный. Это почти наверняка самая стандартная реализация C ++ в мире. Компилятор Intel C ++ использует EDG.
Ричард Корден
1
Какая функция C ++ не имеет «неожиданных побочных эффектов»? В случае экспорта, это то, что безымянная функция пространства имен будет найдена из другого TU - это то же самое, как если бы вы включили определение шаблона напрямую. Было бы более удивительно, если бы это было не так!
Ричард Корден
Я думаю, что у вас есть опечатка - для NS::Sработы, не Sнужно не быть внутри namespace {}?
Эрик
12

Недавно я начал заменять статические ключевые слова анонимными пространствами имен в моем коде, но сразу столкнулся с проблемой, когда переменные в пространстве имен больше не были доступны для проверки в моем отладчике. Я использовал VC60, поэтому я не знаю, не является ли это проблемой с другими отладчиками. Мой обходной путь состоял в том, чтобы определить пространство имен 'module', где я дал ему имя моего файла cpp.

Например, в моем файле XmlUtil.cpp я определяю пространство имен XmlUtil_I { ... }для всех переменных и функций моего модуля. Таким образом, я могу применить XmlUtil_I::квалификацию в отладчике для доступа к переменным. В этом случае _Iон отличается от публичного пространства имен, такого, XmlUtilкоторое я, возможно, захочу использовать в другом месте.

Я предполагаю, что потенциальный недостаток этого подхода по сравнению с действительно анонимным состоит в том, что кто-то может нарушить желаемую статическую область, используя квалификатор пространства имен в других модулях. Я не знаю, является ли это серьезной проблемой, хотя.

Evg
источник
7
Я сделал это тоже, но с #if DEBUG namespace BlahBlah_private { #else namespace { #endif, так что «пространство имен модуля» присутствует только в отладочных сборках, а истинное анонимное пространство имен используется в противном случае. Было бы хорошо, если бы отладчики дали хороший способ справиться с этим. Doxygen смущается этим также.
Кристофер Джонсон
4
безымянное пространство имен не является жизнеспособной заменой статического. static означает «на самом деле это никогда не будет связано с TU». безымянное пространство имен означает «оно все еще экспортируется как случайное имя, в случае, если оно вызывается из родительского класса, который находится вне TU» ...
Эрик Аронести
7

Использование статического ключевого слова для этой цели не рекомендуется стандартом C ++ 98. Проблема со статическим в том, что он не относится к определению типа. Это также перегруженное ключевое слово, используемое по-разному в разных контекстах, поэтому безымянные пространства имен немного упрощают вещи.

Firas Assaad
источник
1
Если вы хотите использовать тип только в одном модуле перевода, объявите его в файле .cpp. В любом случае, он не будет доступен из других модулей перевода.
Кальмарий
4
Вы бы подумали, не так ли? Но если другой модуль перевода (= cpp-файл) в том же приложении когда-либо объявит тип с тем же именем, у вас возникнут довольно трудные для отладки проблемы :-). Например, вы можете столкнуться с ситуациями, когда vtable для одного из типов используется при вызове методов для другого.
avl_sweden
1
Больше не рекомендуется. И определения типов не экспортируются, так что это бессмысленно. Статика полезна для автономных функций и глобальных переменных. безымянные пространства имен полезны для классов.
Эрик Аронесты
6

Из опыта я просто отмечу, что, хотя это способ C ++ поместить ранее статические функции в анонимное пространство имен, старые компиляторы могут иногда иметь проблемы с этим. В настоящее время я работаю с несколькими компиляторами для наших целевых платформ, и более современный компилятор Linux хорошо размещает функции в анонимном пространстве имен.

Но более старый компилятор, работающий на Solaris, который мы используем до неуказанного будущего выпуска, иногда принимает его, а иногда отмечает его как ошибку. Ошибка не в том, что меня беспокоит, а в том, что он может делать, когда принимает это. Поэтому, пока мы не станем современными по всем направлениям, мы все еще будем использовать статические (обычно классовые) функции, где мы предпочитаем анонимное пространство имен.

Дон Уэйкфилд
источник
3

Кроме того, если кто-то использует статическое ключевое слово для переменной, как в этом примере:

namespace {
   static int flag;
}

Это не будет видно в файле сопоставления

Крис
источник
7
Тогда вам вообще не нужно анонимное пространство имен.
Кальмарий
2

Отличие компилятора между анонимными пространствами имен и статическими функциями можно увидеть при компиляции следующего кода.

#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 и выше, будут выдавать предупреждения для функции без ссылок в пространстве имен, а также предупреждение о неиспользуемой статической функции.

Живая демоверсия gcc 4.9 и MSVC 2017

masrtis
источник
2

Лично я предпочитаю статические функции над безымянными пространствами имен по следующим причинам:

  • Из определения функции ясно и ясно, что она является частной по отношению к модулю перевода, где она компилируется. С безымянным пространством имен вам может понадобиться прокрутить и выполнить поиск, чтобы увидеть, находится ли функция в пространстве имен.

  • Некоторые (более старые) компиляторы могут рассматривать функции в пространствах имен как внешние. В VS2017 они все еще внешние. По этой причине, даже если функция находится в безымянном пространстве имен, вы все равно можете пометить их как статические.

  • Статические функции ведут себя очень похоже в C или C ++, тогда как безымянные пространства имен, очевидно, являются только C ++. безымянные пространства имен также добавляют дополнительный уровень отступа, и мне это не нравится :)

Итак, я рад видеть, что использование static для функций больше не считается устаревшим .

Павел П
источник
Предполагается, что функции в анонимных пространствах имен имеют внешнюю связь. Они просто изуродованы, чтобы сделать их уникальными. Только staticключевое слово фактически применяет локальную связь с функцией. Кроме того, конечно, только бредовый сумасшедший фактически добавит отступ для пространств имен?
Roflcopter4
0

Узнав об этой функции только сейчас, читая ваш вопрос, я могу только строить догадки. Похоже, это обеспечивает несколько преимуществ по сравнению со статической переменной на уровне файлов:

  • Анонимные пространства имен могут быть вложены друг в друга, обеспечивая несколько уровней защиты, из которых символы не могут выйти.
  • Несколько анонимных пространств имен могут быть помещены в один и тот же исходный файл, создавая фактически разные области статического уровня в одном и том же файле.

Мне было бы интересно узнать, использовал ли кто-нибудь анонимные пространства имен в реальном коде.

Коммодор егер
источник
4
Хорошие спекуляции, но не так. Область этих пространств имен распространяется на весь файл.
Конрад Рудольф
Не совсем верно, если вы определяете анонимное пространство имен внутри другого пространства имен, оно все еще имеет ширину файла и может рассматриваться только как находящееся в этом пространстве имен. Попробуй это.
Грег Роджерс
Я могу ошибаться, но, я думаю, что нет, это не для всего файла: он доступен только для кода после анонимного пространства имен. Это тонкая вещь, и обычно я не хотел бы загрязнять источник несколькими анонимными пространствами имен ... Тем не менее, это может иметь применение.
paercebal
0

Разница заключается в названии искаженного идентификатора ( _ZN12_GLOBAL__N_11bEvs _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

Льюис Келси
источник