我正在寻找以下情况中的“最佳实践”: 一般来说,有两种常见的方法可以在两个(或更多)非成员函数之间共享私有数据,具有不同的优点和缺点:
// Example 1: using 'static' class
// HPP
namespace foo {
class bar
{
private:
static const char* const s_private;
bar();
public:
static void s_method0();
static void s_method1();
}; /* class bar */
} /* namespace foo */
// CPP
namespace foo {
const char* const bar::s_private = "why do I need to be visible in HPP?";
void bar::s_method0() { std::cout << "s_method0 said: " << s_private << std::endl; }
void bar::s_method1() { std::cout << "s_method1 said: " << s_private << std::endl; }
} /* namespace foo */
// Example 2: using unnamed-namespace
// HPP
namespace foo {
void bar0();
void bar1();
} /* namespace foo */
// CPP
namespace foo {
namespace {
const char* const g_anonymous = "why do I need external linkage?";
} /* unnamed-namespace */
void bar0() { std::cout << "bar0 said: " << g_anonymous << std::endl; }
void bar1() { std::cout << "bar1 said: " << g_anonymous << std::endl; }
} /* namespace foo */
// Example 3: using static keyword in namespace-scope
// HPP
namespace foo {
void bar0();
void bar1();
} /* namespace foo */
// CPP
namespace foo {
static const char* const g_internal = "nobody outside this file can see me and I don't have external linkage";
void bar0() { std::cout << "bar0 said: " << g_internal << std::endl; }
void bar1() { std::cout << "bar1 said: " << g_internal << std::endl; }
} /* namespace foo */
我更喜欢“示例3”,因为它尽可能接近意图。 但现在我正在使用模板化函数运行某些问题。 “示例1”似乎是解决此问题的唯一方法:
// HPP
namespace foo {
class bar
{
private:
static const char* const s_private;
bar();
public:
template<typename T> static void s_method0() { std::cout << "s_method0 said: " << s_private << std::endl; }
template<typename T> static void s_method1() { std::cout << "s_method1 said: " << s_private << std::endl; }
}; /* class bar */
} /* namespace foo */
// CPP
namespace foo {
const char* const bar::s_private = "why do I need to be visible in HPP?";
} /* namespace foo */
这令人不满意。特别是因为还有其他(在这种情况下是方法)非成员函数,它应该在同一个(在这种情况下是类)范围内,不需要访问这个私有数据。
有人知道一个优雅的解决方案吗?
感谢您的帮助。 最好的问候。
答案 0 :(得分:1)
有些不幸的是,这是一个经常出现在模板中的问题。
但我可以建议你在这里过度工程吗?
事实是,无论你看Loki代码(由Andrei Alexandrescu提供)还是Boost代码(臭名昭着的David Abrahams),没有人真的为提供更好的隐私而烦恼。
相反,他们只是依赖约定并使用Private
命名空间(Loki)或detail
命名空间(Boost,有时会使用更长且更具描述性的名称来防止冲突)。
这很烦人,但你在实践中没有太多可以做的......虽然我实际上有一个解决你的具体问题的方法;)
// Evil solution!
#ifdef MY_SUPER_MACRO
# error "MY_SUPER_MACRO is already defined!"
#endif
#define MY_SUPER_MACRO "Some string"
template <typename T> void foo() { std::cout << "foo - " MY_SUPER_MACRO "\n"; }
template <typename T> void bar() { std::cout << "bar - " MY_SUPER_MACRO "\n"; }
#undef MY_SUPER_MACRO
然后跳,我在带有邪恶宏的标题中获得了地点:)
答案 1 :(得分:0)
怎么样?
namespace foo {
namespace detail {
class shared
{
template<typename> friend void bar0();
template<typename> friend void bar1();
static const char* const m_private;
}; /* class shared */
} /* namespace detail */
template<typename T> void bar0() { std::cout << "bar0 said: " << detail::shared::m_private << std::endl; }
template<typename T> void bar1() { std::cout << "bar1 said: " << detail::shared::m_private << std::endl; }
} /* namespace foo */
编辑2: 已废除的答案。这个代码不起作用!张贴下面的说明。
编辑: 在'真实代码'中,我将用未命名的命名空间替换命名空间细节。这样就可以使用相同的名称范围在不同的头文件中添加其他共享资源:
namespace foo {
template<typename T> void bar0();
template<typename T> void bar1();
template<typename T> void bar2();
template<typename T> void bar3();
namespace {
class shared0
{
template<typename> friend void foo::bar0();
template<typename> friend void foo::bar1();
static const char* const m_private0;
}; /* class shared0 */
class shared1
{
template<typename> friend void foo::bar2();
template<typename> friend void foo::bar3();
static const char* const m_private1;
}; /* class shared1 */
} /* unnamed */
template<typename T> void bar0() { std::cout << "bar0 said: " << shared0::m_private0 << std::endl; }
template<typename T> void bar1() { std::cout << "bar1 said: " << shared0::m_private0 << std::endl; }
template<typename T> void bar2() { std::cout << "bar0 said: " << shared1::m_private1 << std::endl; }
template<typename T> void bar3() { std::cout << "bar1 said: " << shared1::m_private1 << std::endl; }
} /* namespace foo */
答案 2 :(得分:0)
如果我理解正确,你的抱怨/担心是与模板不同,使用非模板,可以在CPP中定义函数体,而不是标题,在这种情况下,他们可以访问非类静态,对外界是“不可见的”,包括标题中定义的成员函数。这一切都是真的。
但是,请记住,在CPP中也没有什么可以阻止定义其他成员函数,在这种情况下,他们同样能够访问静态数据。所以真的,情况也不例外。您的投诉是基于错误的二分法。
如果您真的想要阻止任何但s_method0<T>()
和s_method1<T>()
访问s_private
,那么您必须将它们全部放在专门的课程中。就这么简单。即使对于非模板也是如此。
答案 3 :(得分:0)
我玩过不同的技巧。我的想法是,在头文件中使用未命名的命名空间,将'shared'类标记为'header-file-only'。当然,由于它们不包含公共成员,因此无论如何都不能制作令人讨厌的东西。但我认为它会更接近意图。
但我错了!想到这一点后,我很惭愧。这在逻辑上很简单。这个例子显示了它的问题(整个代码清晰):
// 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;
}
结果是,我们以2个未解析的外部结尾。那么,链接器只是一个白痴吗?他不是。考虑到什么是未命名的命名空间,我们知道发生了什么。未命名的命名空间在每个翻译单元中具有唯一标识符。因此,在我们的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";
// ...
我期待着任何评论。最好的问候。