C++没有Java中“package”概念的等价物。在Java中,"package"是由一些代码随意组合形成的一个集合,仅通过打包在一起定义。
因此,“package private”有点儿嘲讽封装的概念。是的,访问范围在某种程度上是“受限”的,但它仍然大部分是无界的。因此,最好不要把这个特性添加到语言中。
虽然C++没有提供“package”概念,但有方法允许特定的代码包调用其他任意代码包不能调用的函数,这需要使用“key type”惯用语法。
“key type”指的是通常为空的类型,其主要特征是只能由某些代码创建该类型的对象。因此,任何以这种类型作为参数的函数只能由能够创建关键类型的代码调用。该类型因此“解锁”了函数,因此得名。
传统用法是允许私有访问在C++中通过 "emplace "和类似的完美转发结构进行转发。关键类型的默认构造函数被设置为“private”,只有关键类型的显式“friend”才能创建它。但由于该类型是公共可复制的,任何转发函数都可以将它们复制到目标位置。
在你的情况下,你希望关键类型只能从某些文件中的代码构造。为此,你只需提供一个头文件,定义关键类型,通常是一个简单的空类。在“package”的公共头文件中,任何你想让它成为“package private”的函数都将以“const&”参数接受一个“package_private”。
但是,“package”的公共头文件不包括“package_private”的定义;仅仅是对它进行了前向声明。这意味着只有访问公共头文件的代码是无法创建该类型对象的。他们可以看到typename,但他们不能做任何事情。
所以它可能看起来像这样:
struct package_private {};
inline constexpr static package_private priv;
struct package_private;
void package_private_function(package_private const&, ...);
package_private_function(priv, ...);
library::package_private priv{};
library::package_private_function(priv, ...);
由于C++就是C++, 用户总能够作弊:
alignas(max_align_t) char data[sizeof(max_align_t)];
library::package_private &key = *reinterpret_cast<library::package_private*>(&data);
instance.pack_priv_function(key, ...);
在C++20中,只要`package_private`在`data`的给定对齐和大小内并且是隐式生命周期类型,这甚至不是未定义行为。您可以执行某些操作,使`package_private`不是这些东西,但那只会让此代码成为未定义行为。它仍然可以编译并几乎肯定仍然有效;毕竟,该函数从来没有“访问”此对象。
向用户提示头文件中某些类型是内部类型并且不应被外部代码使用的传统方法是将其放入`detail`命名空间中。
C++20模块提供了一种方式来防止一类破坏这个规则的方式。如果我们将一个模块视为一个“包”,那么你所要做的就是不导出`package_private`类型。它仍然可以列在需要导出的函数的参数中(它们不再需要是`const&`)。但`package_private`类型本身不会被导出。
模块内的代码可以使用该名称;您可以将定义放入实现分区中,该分区由需要此访问权限的任何模块内文件进行`import`。但是,在`import`它的代码之外的模块不能使用该名称,因此它们甚至无法执行上面显示的强制转换技巧。有一些元编程技术可以在不知道其类型的情况下检查函数的签名,但那些技术非常困难,并且会被重载破坏。
另一方面,Java反射可以破坏任何封装性,所以“包私有”并不是绝对可靠的。