Безіменні / анонімні простори імен та статичні функції


507

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

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

Ви можете подумати, що така функція буде марною - оскільки ви не можете вказати назву простору імен, неможливо отримати доступ до чого-небудь всередині неї ззовні. Але ці безіменні простору імен є доступними в файлі вони створили в, як якщо б ви мали неявне з допомогою придаткового до них.

Моє запитання: чому або коли це було б кращим, ніж використання статичних функцій? Або вони по суті є двома способами зробити саме те саме?


13
У C ++ 11 використання staticв цьому контексті було недооціненим ; хоча неназваний простір імен є чудовою альтернативоюstatic , є випадки, коли він не працює, коли staticна допомогу .
legends2k

Відповіді:


332

Стандарт C ++ читається в розділі 7.3.1.1, безіменні простори імен, параграф 2:

Використання статичного ключового слова припинено при оголошенні об'єктів в області простору імен, простір безіменних імен надає чудову альтернативу.

Статичний характер стосується лише імен об'єктів, функцій та анонімних об'єднань, а не для введення декларацій.

Редагувати:

Рішення про відмову від використання статичного ключового слова (впливати на видимість змінної декларації в блоці перекладу) було скасовано ( посилання ). У цьому випадку використання статичного чи безіменного простору імен повертаються до принципово двох способів зробити точну саму річ. Для більш детальної дискусії дивіться це питання ТАК.

Переважно неіменовані простори імен дозволяють визначати локальні типи перекладу-одиниці. Будь ласка, дивіться це питання ТА для отримання більш детальної інформації.

Кредит приділяється Майку Персі за те, що він довів це до моєї уваги.


39
Head Geek запитує про статичне ключове слово, яке використовується лише для функцій. Статичне ключове слово, застосоване до об'єкта, оголошеного в області простору імен, визначає його внутрішню зв'язок. Суб'єкт, декларований в анонімному просторі імен, має зовнішню зв'язок (C ++ / 3.5), однак гарантовано, що він житиме в унікальному імені. Ця анонімність простої імені без імені ефективно приховує його декларацію, роблячи її доступною лише з одиниці перекладу. Останній ефективно працює так само, як і статичне ключове слово.
mloskot

5
у чому недолік зовнішньої зв'язку? Чи може це вплинути на вбудовування?
Олексій

17
Ті, хто в дизайнерській комісії C ++, хто сказав, що статичне ключове слово є застарілим, ймовірно, ніколи не працювали з величезним кодом С у великій системі реального світу ... (Ви негайно бачите статичне ключове слово, але не анонімний простір імен, якщо він містить багато декларацій з великим коментарем блоки.)
Кальмарій

23
Оскільки ця відповідь з'являється в Google як найкращий результат для "c ++ анонімного простору імен", слід зазначити, що використання статики більше не застаріло. Див stackoverflow.com/questions/4726570 / ... і open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012 для отримання додаткової інформації.
Майкл Персі

2
@ErikAronesty Це звучить неправильно. Чи є у вас відтворюваний приклад? Що стосується C ++ 11 - і навіть до цього у деяких компіляторах - неназвані namespaces неявно мають внутрішній зв'язок, тому різниці не повинно бути. Будь-які проблеми, які раніше могли виникнути через неякісні формулювання, були вирішені, зробивши це вимогою в C ++ 11.
підкреслити_13

73

Якщо розмістити методи в анонімному просторі імен, ви запобігаєте випадковому порушенню Правила однієї дефініції , що дозволяє вам ніколи не хвилюватися щодо того, щоб називати свої допоміжні методи так само, як і інші способи, з якими ви можете зв’язатись.

І, як вказує luke, анонімні простори імен надаються перевагою стандарту над статичними елементами.


2
Я мав на увазі статичні автономні функції (тобто функції, що охоплюють файли), а не статичні функції членів. Статичні автономні функції майже такі ж, як і функції в неназваному просторі імен, тому питання.
Head Geek

2
Ах; ну, ODR все ще застосовується. Відредаговано для видалення абзацу.
hazzen

Як я зрозумів, ODR для статичної функції не працює, коли це визначено в заголовку, і цей заголовок включений у більш ніж один блок перекладу, правда? у цьому випадку ви отримуєте кілька копій однієї функції
Андрій Тиличко,

@Andy T: Ви дійсно не бачите "декількох визначень" у разі включеного заголовка. Препроцесор дбає про це. Якщо тільки немає необхідності у вивченні результатів, які генерував препроцесор, що для мене виглядає досить екзотично і рідко. Також є хороша практика включати "охоронці" у файли заголовків, як-от: "#ifndef SOME_GUARD - #define SOME_GUARD ...", яка повинна не дозволяти препроцесору включати один і той же заголовок двічі.
Микита Воронцов

@NikitaVorontsov захист може запобігти включенню одного і того ж заголовка в один і той же блок перекладу, однак він дозволяє кілька визначень у різних одиницях перекладу. Це може спричинити помилку лінкера "кілька визначень" внизу лінії.
Алекс

37

Є один крайній випадок, коли статика має дивовижний ефект (принаймні, це було для мене). Стандарт 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.

Оновлення для сучасного C ++

Станом на C ++ '11, члени простої імені безіменного мають внутрішній зв'язок неявно (3.5 / 4):

Неназваний простір імен або простір імен, оголошений прямо або опосередковано в просторі імен без імені, має внутрішні зв'язки.

Але в той же час 14.6.4.2/1 було оновлено, щоб видалити згадування про зв'язок (це взято з C ++ '14):

Для виклику функції, де постфікс-вираз є залежним іменем, функції-кандидати знаходять за допомогою звичайних правил пошуку (3.4.1, 3.4.2), за винятком того, що:

  • Для частини пошуку з використанням некваліфікованого пошуку імен (3.4.1) знаходять лише декларації функції з контексту визначення шаблону.

  • Для частини пошуку з використанням пов'язаних просторів імен (3.4.2) знаходять лише декларації функцій, які знаходяться або в контексті визначення шаблону, або в контексті інстанції шаблону.

В результаті цього особливої ​​різниці між статичними та безіменними членами простору імен вже не існує.


3
Хіба ключове слово експорту не повинно бути мертвим? Єдині компілятори, що підтримують "експорт", - це експериментальні, і якщо не буде сюрпризів, "експорт" навіть не буде реалізований в інших через несподівані побічні ефекти (окрім того, що це не було зроблено, як очікувалося)
paercebal

2
Дивіться статтю Герба Саттера про subjet
paercebal

3
Передній край від Edison Design Group (EDG) - це все, крім експериментального. Це майже напевно найбільш стандартна реалізація C ++ у світі. Компілятор Intel C ++ використовує EDG.
Річард Корден

1
Яка функція C ++ не має "несподіваних побічних ефектів"? У випадку експорту, це те, що неназвана функція простору імен буде знайдена з іншого TU - це те саме, що якби ви безпосередньо включили визначення шаблону. Було б дивніше, якби не так!
Річард Корден

Я думаю, у вас там помилка друку - NS::Sщоб працювати, чи не Sпотрібно бути там всередині namespace {}?
Ерік

12

Нещодавно я почав замінювати статичні ключові слова анонімними просторами імен у своєму коді, але негайно зіткнувся з проблемою, коли змінні в просторі імен більше не були доступні для перевірки в моєму налагоджувачі. Я використовував VC60, тому не знаю, чи це проблема не з іншими налагоджувачами. Моє вирішення полягало у визначенні простору імен 'модуль', де я назвав його моєму файлу cpp.

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

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


7
Я теж це робив, але з #if DEBUG namespace BlahBlah_private { #else namespace { #endif, тому "простір імен модулів" присутній лише у налагодженнях, а справжній анонімний простір імен використовується інакше. Було б добре, якби налагоджувачі дали приємний спосіб впоратися з цим. Доксиген його також плутає.
Крістофер Джонсон

4
неназваний простір імен насправді не є життєздатною заміною статичного. статичний означає "справді це ніколи не пов'язане між собою ТУ". неназваний простір імен означає "його все-таки експортують, як випадкове ім'я, якщо його викликають з батьківського класу, який знаходиться поза ТУ" ...
Ерік Аронесті

7

Використання статичних ключових слів для цієї мети вичерпано стандартом C ++ 98. Проблема статики полягає в тому, що вона не стосується визначення типу. Це також перевантажене ключове слово, яке використовується по-різному в різних контекстах, тому неназвані простори імен дещо спрощують речі.


1
Якщо ви хочете використовувати тип лише в одній одиниці перекладу, оголосьте його всередині .cpp-файлу. Він не буде доступний для інших перекладацьких підрозділів.
Кальмарій

4
Ви б подумали, чи не так? Але якщо інший блок перекладу (= cpp-файл) у тому самому додатку коли-небудь оголошує тип з тим же ім’ям, у вас виникають досить важкі проблеми налагодження :-). Наприклад, у вас може виникнути ситуації, коли vtable для одного з типів використовується при виклику методів іншого.
avl_sweden

1
Більше не застаріло. І тип defs не експортується, тому це безглуздо. статика корисна для автономних функцій та глобальних змін. простори імен без імені корисні для занять.
Ерік Аронестій

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-функцією або -Wall показує, що VS 2017 видасть лише попередження для невикористану статичну функцію. gcc 4.9 і новіші, а також кланг 3.3 і новіші, створюватиме попередження про невиділену функцію в просторі імен, а також попередження про невикористану статичну функцію.

Демонстраційна демонстрація gcc 4.9 та MSVC 2017


2

Особисто я віддаю перевагу статичним функціям над безіменними просторами імен з наступних причин:

  • З самого визначення функції очевидно і зрозуміло, що це приватне підрозділ перекладу, де він складений. З безіменним простором імен вам може знадобитися прокрутка та пошук, щоб побачити, чи є функція в просторі імен.

  • Деякі (старіші) компілятори функції в просторах імен можуть розглядатися як зовнішні. У VS2017 вони все ще зовнішні. З цієї причини, навіть якщо функція знаходиться в безіменному просторі імен, ви все одно можете позначати їх статичними.

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

Тож я радий бачити, що використання статики для функцій вже не застаріло .


Функції в анонімних просторах імен повинні мати зовнішній зв'язок. Вони просто налаштовані, щоб зробити їх унікальними. Тільки 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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.