C++中与Java的"package private"等效的是什么?

6
什么是C++中的Java“package private”等效功能? Java包私有性特性(仅同包内的类可见)在提供API时非常有用。
在C++中是否有类似的功能(除了将其他类声明为“友元”之外)? 举个例子,假设A.h和B.h在同一个包中(API库) 文件: A.h
class A
{
public :
void doA();

private : 
 int m_valueA;
};

文件:B.h

class B
{
public : 
void doB()

private:
int m_valueB;

}

我需要的是:

公开可见性:仅限 A::doA() 和 B::doB()

在包内(例如 API 库):A 应该能够访问 B::m_valueB,B 应该能够访问 A::m_valueA。 而不使彼此成为“友元”类。


5
没有,没有这样的事情。 - user253751
3
不熟悉Java,您能简要解释一下“包私有(Package Private)”是什么吗?或者这个解释对非Java程序员来说太长了,不容易理解? - user10957435
@Kitswas:没有更多的细节需要提供了。C++没有这样的功能,也没有类似的功能。这就是事实。 - Nicol Bolas
@NicolBolas 是否有可能模拟这种行为? - Kitswas
5个回答

5

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,但他们不能做任何事情。

所以它可能看起来像这样:

//Internal header, included by all code in the "package"
struct package_private {};
inline constexpr static package_private priv; //Makes it easier to call these functions

//Header for library.
struct package_private;
void package_private_function(package_private const&, ...); //Must be `const&` to avoid needing to define `package_private`.

//To call the package private function inside the library:
package_private_function(priv, ...);

//This is a compile error for any code that doesn't have the internal header:
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反射可以破坏任何封装性,所以“包私有”并不是绝对可靠的。

2
C++没有像Java那样的包。但是它有名称空间,不过名称空间只是一个“名称空间”。因此,它是一种不同的东西。在某些情况下,内部类(嵌套在其他类中的类)可以在某种程度上模拟 - 因为内部类被视为成员变量。此外,还有头文件和实现(.cpp 文件) - 在这个意义上,你有单元或模块来控制实际可见性(不仅仅是私有的,而是完全隐藏的- 特别是如果放到匿名名称空间中)。这个概念涵盖了一个.h 文件和.cpp 文件或整个项目/libs/dlls,更像一个完整的包(可以通过在各自的头文件中显示的内容来选择他们公开API的哪些部分)。"最初的回答"

@darune,谢谢。但是“匿名命名空间”只适用于单个文件,对吧?那么如何实现“...或整个项目/库/动态链接库,更像是一个完整的包”? - aKumara
@aKumara 大多数传统库都会有一些内部部分(一些仅包含头文件的库将内部内容放在“private”或“internal”子命名空间中的“*_impl.hpp”文件中 - 但是没有编译器强制执行的机制来防止其使用)。 - darune

1
您可能对C++中的PIMPL习语感兴趣,正如@darune所说,它与等效但在语义上接近。通常,您会这样做:
在YourPublicClass.hpp中:
class MyPublicClass
{
    // Public interface
public:
   void doSomething();
   
   void manipulatePrivateStuff(Stuff * stuff);

   MyPublicClass(...);
   ~MyPublicClass();

   struct Stuff;  // <= This is were the magic happens, this stuff 
                  //    is unknown/private from who include this header

private: 
   Stuff * _member;
};

在您的公共类(YourPublicClass.cpp)中:
#include <iostream>
#include "YourPublicClass.hpp"

struct MyPublicClass::Stuff
{
    // Public members that are only accessible from this compilation unit
    // but private from the rest of the code, like a private package

    int a;
    
    void explodeInTenSeconds() { if (!a--) std::cout<<"Boom!"<<std::endl; }

    Stuff(int delay = 10) : a(delay) {}
};

void MyPublicClass::doSomething() { _member->explodeInTenSeconds(); }
void MyPublicClass::manipulatePrivateStuff(Stuff * stuff) { stuff->a = 10; }

MyPublicClass::MyPublicClass(...) : _member(new Stuff(10)) {}
MyPublicClass::~MyPublicClass() { delete(_member); }


如果你需要另一个类来访问“包私有”Stuff,你需要将MyPublicClass::Stuff声明移动到它自己的头文件中,并在这个类的定义文件(.cpp)中包含该头文件。这个头文件不应该在“包”之外被包含,它不是公共的。编译器只需要知道这是一个指向未指定结构体的指针,因此无需进行操作。

0

C++没有包。

这意味着所请求的行为“在我的包内部的其他代码与包外的代码具有不同的访问权限”甚至没有意义。因为根本不存在“我的包”,所以也就不存在“我的包内部的代码”。

更进一步地,C++中的private访问修饰符符合Java中package private的规范。对于包外的代码来说是不可访问的(private),就像Java中的package private一样。对于同一包内的代码来说,它是可以轻松访问的——因为根本不存在这样的代码。

显然,这对于建立协作关系并不实用。但当你试图提出那些只在其他语言中有意义的问题时,这就是你得到的答案。你的问题还有一个方面是Java中心化的,并且会对思考C++造成伤害,那就是你认为所有的代码都组织成类。在C++中并非如此,还有自由函数和相关函数(通过ADL)不是类成员。


0
首先,理解什么是私有包很重要。这意味着同一包中的其他成员可以访问该项。在Java中,包是指通过捆绑在一起来定义的任意代码集合。
然而,在C++中,“private-package”这个特性并没有Java语言中的相当之处。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接