Почему типы должны быть помещены в безымянные пространства имен?

Я понимаю использование безымянных пространств имен, чтобы сделать функции и переменные внутренними связями. Безымянные пространства имен не используются в заголовочных файлах; только исходные файлы. Типы, объявленные в исходном файле, не могут использоваться снаружи. Так какая польза от размещения типов в безымянных пространствах имен?

Посмотрите эти ссылки, где упоминается, что типы могут быть помещены в безымянные пространства имен:

8

Решение

Куда вы хотите поместить локальные типы, отличные от безымянного пространства имен? Типы не могут иметь спецификатор связи, например 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 должно состоять из одной и той же последовательности токенов; а также
[…]

Существует множество дополнительных ограничений, но «должно состоять из одной и той же последовательности токенов», очевидно, достаточно, чтобы исключить, например, Определения в демо выше из законности.

13

Другие решения

Так какая польза от размещения типов в безымянных пространствах имен?

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

Например, я часто использую два класса в безымянных пространствах имен — 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() {}
};

Это нормально для простого случая, подобного этому, поскольку реализации идентичны. Если встроенные реализации отличаются, программа подвержена неопределенному поведению.

4

Возможно, я немного опоздал, чтобы ответить на вопрос ОП, но, поскольку я думаю, что ответ не совсем ясен, я хотел бы помочь будущим читателям.

Давайте попробуем проверить … скомпилируйте следующие файлы:

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

Это только мое предположение.

0
По вопросам рекламы [email protected]