Я понимаю использование безымянных пространств имен, чтобы сделать функции и переменные внутренними связями. Безымянные пространства имен не используются в заголовочных файлах; только исходные файлы. Типы, объявленные в исходном файле, не могут использоваться снаружи. Так какая польза от размещения типов в безымянных пространствах имен?
Посмотрите эти ссылки, где упоминается, что типы могут быть помещены в безымянные пространства имен:
Куда вы хотите поместить локальные типы, отличные от безымянного пространства имен? Типы не могут иметь спецификатор связи, например static
, Если они не известны широкой публике, например, потому что они объявлены в заголовке, существует большая вероятность того, что имена локальных типов конфликтуют, например, когда две единицы перевода определяют типы с одинаковым именем. В этом случае вы получите нарушение ODR. Определение типов внутри безымянного пространства имен исключает эту возможность.
Чтобы быть немного конкретнее. Считайте, у вас есть
// file demo.h
int foo();
double bar();
// file foo.cpp
struct helper { int i; };
int foo() { helper h{}; return h.i; }
// file bar.cpp
struct helper { double d; }
double bar() { helper h{}; return h.d; }
// file main.cpp
#include "demo.h"int main() {
return foo() + bar();
}
Если вы свяжете эти три единицы перевода, вы получите несоответствующие определения helper
от foo.cpp
а также bar.cpp
, Компилятор / компоновщик не обязан обнаруживать их, но каждый тип, который используется в программе, должен иметь согласованное определение. Нарушение этих ограничений известно как нарушение «правила одного определения» (ODR). Любое нарушение правила ODR приводит к неопределенному поведению.
Учитывая комментарий, кажется, нужно немного убедительнее. Соответствующий раздел стандарта — 3.2 [basic.def.odr] параграф 6:
Может быть более одного определения типа класса (раздел 9), типа перечисления (7.2), встроенной функции с внешней связью (7.1.2), шаблона класса (раздел 14), шаблона нестатической функции (14.5.6) член статических данных
шаблона класса (14.5.1.3), функция-член шаблона класса (14.5.1.1) или специализация шаблона, для которого некоторые параметры шаблона не указаны (14.7, 14.5.5) в программе при условии, что каждое определение появляется в различные единицы перевода, и при условии, что определения удовлетворяют следующим требованиям. Если такой объект с именем D определен более чем в одной единице перевода, то каждое определение D должно состоять из одной и той же последовательности токенов; а также
[…]
Существует множество дополнительных ограничений, но «должно состоять из одной и той же последовательности токенов», очевидно, достаточно, чтобы исключить, например, Определения в демо выше из законности.
Так какая польза от размещения типов в безымянных пространствах имен?
Вы можете создавать короткие значимые классы с именами, которые могут использоваться в нескольких файлах, без проблемы конфликтов имен.
Например, я часто использую два класса в безымянных пространствах имен — Initializer
а также Helper
,
namespace
{
struct Initializer
{
Initializer()
{
// Take care of things that need to be initialized at static
// initialization time.
}
};
struct Helper
{
// Provide functions that are useful for the implementation
// but not exposed to the users of the main interface.
};
// Take care of things that need to be initialized at static
// initialization time.
Initializer initializer;
}
Я могу повторить этот шаблон кода в любом количестве файлов без имен Initializer
а также Helper
мешать.
Обновление, в ответ на комментарий от OP
файл-1.cpp:
struct Initializer
{
Initializer();
};
Initializer::Initializer()
{
}
int main()
{
Initializer init;
}
файл-файл 2.cpp:
struct Initializer
{
Initializer();
};
Initializer::Initializer()
{
}
Команда для сборки:
g++ file-1.cpp file-2.cpp
Я получаю сообщение об ошибке компоновщика о нескольких определениях Initializer::Initializer()
, Обратите внимание, что стандарт не требует компоновщика для создания этой ошибки. Из раздела 3.2 / 4:
Каждая программа должна содержать ровно одно определение каждой не встроенной функции или переменной, которая используется в этой программе в виде odr; Диагностика не требуется.
Компоновщик не выдает ошибку, если функции определены inline:
struct Initializer
{
Initializer() {}
};
Это нормально для простого случая, подобного этому, поскольку реализации идентичны. Если встроенные реализации отличаются, программа подвержена неопределенному поведению.
Возможно, я немного опоздал, чтобы ответить на вопрос ОП, но, поскольку я думаю, что ответ не совсем ясен, я хотел бы помочь будущим читателям.
Давайте попробуем проверить … скомпилируйте следующие файлы:
//main.cpp
#include <iostream>
#include "test.hpp"
class Test {
public:
void talk() {
std::cout<<"I'm test MAIN\n";
}
};
int main()
{
Test t;
t.talk();
testfunc();
}
//test.hpp
void testfunc();
//test.cpp
#include <iostream>
class Test {
public:
void talk()
{
std::cout<<"I'm test 2\n";
}
};void testfunc() {
Test t;
t.talk();
}
Теперь запустите исполняемый файл.
Вы ожидаете увидеть:
I'm test MAIN
I'm test 2
Что вы должны увидеть мысль:
I'm test MAIN
I'm test MAIN
Что случилось?!?!!
Теперь попробуйте поместить безымянное пространство имен вокруг класса «Test» в «test.cpp» следующим образом:
#include <iostream>
#include "test.hpp"
namespace{
class Test {
public:
void talk()
{
std::cout<<"I'm test 2\n";
}
};
}
void testfunc() {
Test t;
t.talk();
}
Скомпилируйте его снова и запустите.
Выход должен быть:
I'm test MAIN
I'm test 2
Вот Это Да! Оно работает!
Оказывается, это является важно определить классы внутри безымянных пространств имен, чтобы вы могли получить из них надлежащую функциональность, когда два имени классов в разных единицах перевода идентичны.
Теперь что касается Зачем это так, я не проводил никаких исследований по этому вопросу (может, здесь кто-то мог бы помочь?), и поэтому я не могу точно сказать вам наверняка. Я отвечаю чисто с практической точки зрения.
Что я подозреваю, хотя это то, что это правда что структуры C действительно локальны для модуля перевода, они немного отличаются от классов, поскольку классам в c ++ обычно присваивается поведение. Поведение означает функции и, как мы знаем, функции не местный по отношению к отделу перевода.
Это только мое предположение.