Я играл с разными техниками. Моя идея, используя безымянное пространство имен в заголовочном файле, заключалась в том, чтобы «пометить» «общие» классы как «только заголовочный файл». Конечно, из-за того, что они не содержат публичных участников, вы все равно не сможете делать неприятных вещей. Но я думал, что это будет ближе к цели.
Но я был не прав! Подумав об этом, мне стыдно. Это так логично просто. Этот пример показывает, в чем проблема (весь код для ясности):
// header0.hpp
#ifndef HPP_HEADER0_INCLUDED
#define HPP_HEADER0_INCLUDED
#include <iostream>
#include <string>
namespace ns {
template<typename T> void header0_func0();
template<typename T> void header0_func1();
namespace {
class header0
{
template<typename> friend void ns::header0_func0();
template<typename> friend void ns::header0_func1();
static std::string s_private;
}; /* class header0 */
} /* unnamed */
template<typename T> void header0_func0() { std::cout << "header0_func0: " << header0::s_private << std::endl; }
template<typename T> void header0_func1() { std::cout << "header0_func1: " << header0::s_private << std::endl; }
} /* namespace ns */
#endif /* HPP_HEADER0_INCLUDED */
// header1.hpp
#ifndef HPP_HEADER1_INCLUDED
#define HPP_HEADER1_INCLUDED
#include <iostream>
#include <string>
namespace ns {
template<typename T> void header1_func0();
template<typename T> void header1_func1();
namespace {
class header1
{
template<typename> friend void ns::header1_func0();
template<typename> friend void ns::header1_func1();
static std::string s_private;
}; /* class header1 */
} /* unnamed */
template<typename T> void header1_func0() { std::cout << "header1_func0: " << header1::s_private << std::endl; }
template<typename T> void header1_func1() { std::cout << "header1_func0: " << header1::s_private << std::endl; }
} /* namespace ns */
#endif /* HPP_HEADER1_INCLUDED */
// source.cpp
#include "header0.hpp"
#include "header1.hpp"
std::string ns::header0::s_private = "header0 private data definition by source.cpp",
ns::header1::s_private = "header1 private data definition by source.cpp";
namespace {
// hide private class
class source
{
source();
~source();
static source s_instance;
};
source::source() {
std::cout << "source.cpp:\n";
ns::header0_func0<int>();
ns::header0_func1<int>();
ns::header1_func0<int>();
ns::header1_func1<int>();
std::cout << '\n';
}
source::~source() { }
source source::s_instance;
} /* unnamed */
К настоящему времени все вроде бы в порядке. Но что произойдет, если мы попытаемся использовать наши заголовки в других единицах перевода? Давайте посмотрим:
// main.cpp
#include "header0.hpp"
#include "header1.hpp"
int main()
{
std::cout << "main.cpp:\n";
ns::header0_func0<int>();
ns::header0_func1<int>();
ns::header1_func0<int>();
ns::header1_func1<int>();
std::cout << '\n';
return 0;
}
Что происходит, так это то, что мы заканчиваем двумя неразрешенными внешними явлениями. Так что, компоновщик просто идиот? Нет он не. Размышляя о том, для чего используется безымянное пространство имен, мы знаем, что происходит. Безымянное пространство имен имеет уникальный идентификатор в каждой единице перевода. Таким образом, в нашем main.cpp компоновщик не знает определения наших личных данных в source.cpp.
Итак, что произойдет, если мы определим эти личные данные в main.cpp - просто чтобы разобраться в этом -?
// main.cpp
#include "header0.hpp"
#include "header1.hpp"
std::string ns::header0::s_private = "header0 private data definition by main.cpp",
ns::header1::s_private = "header1 private data definition by main.cpp";
int main()
{
std::cout << "main.cpp:\n";
ns::header0_func0<int>();
ns::header0_func1<int>();
ns::header1_func0<int>();
ns::header1_func1<int>();
std::cout << '\n';
return 0;
}
Теперь все компилируется и связывается «правильно», или, скорее, так оно и есть.
Это консольный вывод этой программы:
source.cpp:
header0_func0: header0 private data definition by source.cpp
header0_func1: header0 private data definition by source.cpp
header1_func0: header1 private data definition by source.cpp
header1_func0: header1 private data definition by source.cpp
main.cpp:
header0_func0: header0 private data definition by source.cpp
header0_func1: header0 private data definition by source.cpp
header1_func0: header1 private data definition by source.cpp
header1_func0: header1 private data definition by source.cpp
Это означает, что если вы ищете неопределенное поведение, то вы здесь.
Другими словами: на основе приведенного выше объяснения: не используйте безымянное пространство имен в заголовочных файлах для инкапсуляции личных общих данных.
И последний вопрос: «Какое решение?»
Если вы не хотите использовать «статические» (служебные) классы, вы должны предпочесть мое первое опубликованное решение (только измененный код):
// header0.hpp
// ...
namespace ns {
// ...
namespace detail {
class header0 { /*...*/ };
} /* namespace detail */
template<typename T> void header0_func0() { std::cout << "header0_func0: " << detail::header0::s_private << std::endl; }
template<typename T> void header0_func1() { std::cout << "header0_func1: " << detail::header0::s_private << std::endl; }
} /* namespace ns */
// ...
// header1.hpp
// ...
namespace ns {
// ...
namespace detail {
class header1 { /*...*/ };
} /* namespace detail */
template<typename T> void header0_func0() { std::cout << "header0_func0: " << detail::header1::s_private << std::endl; }
template<typename T> void header0_func1() { std::cout << "header0_func1: " << detail::header1::s_private << std::endl; }
} /* namespace ns */
// ...
// source.cpp
// ...
std::string ns::detail::header0::s_private = "header0 private data definition by source.cpp",
ns::detail::header1::s_private = "header1 private data definition by source.cpp";
// ...
Я с нетерпением жду любого комментария. С наилучшими пожеланиями.