C++私有成员真的是私有的吗?

19

我正在尝试验证C++中private访问修饰符的有效性。以下是我的实验代码:

接口:

// class_A.h

class A
{
public:
    void printX();
private:
    void actualPrintX();
    int x;
};

实现:

// class_A.cpp
void A::printX()
{
    actualPrintX();
}

void A::actualPrintX()
{
    std::cout << x:
}

我将这个程序编译成了一个静态库 (.a/.lib)。我们现在有一个 class_A.h 和 classA.a (或 classA.lib) 配对。

我编辑了 class_A.h 并将其中的 private: 删除了。

现在在另一个 classTester.cpp 文件中:

#include "class_A.h"    // the newly edited header

int main()
{
    A a;

    a.x = 12;           // both G++ and VC++ allowed this!
    a.printX();         // allowed, as expected
    a.actualPrintX();   // allowed by G++, VC++ gave a unresolved linker error

    return 0;
}

我知道在篡改库的头文件之后,一切都无法预测(我是指系统的完整性等)。虽然这种方法很土,但这真的被允许吗?有没有办法阻止这种情况发生?还是说我在做错了什么?


我知道在这里可以使用柴郡猫(Pimpl - private impl. http://en.wikipedia.org/wiki/Cheshire_Cat_Idiom_%28programming_technique%29)设计,并且访问说明符是编译器的编译时保护。 - legends2k
你为什么不能重新构建它呢? - Dominic Rodger
@Dominic:我的意图是看看如果我尝试访问类的私有部分会发生什么。 - legends2k
1
私有成员仍然存在于内存中。当然,如果你足够熟练,你可以访问私有数据。当然,语言并不允许这样做。但是如果你想作弊,语言或编译器无法阻止你。 - jalf
@jalf:谢谢jalf,总的来说,我明白了通过这样做,库的客户端用户进入了未定义的领域;这已经足够好的理由不去这么做。Gagge的回答清楚地表明这不是一种安全机制,而是一种意图说明符。 - legends2k
9个回答

30

private不是一种安全机制,它是一种传达意图和隐藏其他程序部分不需要知道的信息的方式,从而降低了整体复杂度。

拥有两个不同的头文件不符合标准,因此在技术上,您正在进入未定义行为领域,但实际上,正如您发现的那样,大多数编译器不会关心。


9
你已经超出了C++所允许的范围,因此你正在做的是不被允许的 - 当然,在某些编译器和情况下可能会起作用。
具体来说,你正在违反一次定义规则
Herb Sutter的文章很好地解释了它 - 它还提供了一种合法且可移植的绕过访问限定符系统的方法。

但我没有定义超过一次; 同意,我篡改了声明,但它怎么会成为ODR违规的情况呢? - legends2k
2
你已经定义了 class A 两次 - 一次在 classTester.cpp 翻译单元中,另一次在 class_A.cpp 翻译单元中。ODR 违规是这两个定义不相同。 - JoeG
我终于看明白了!感谢Sutter在同一问题上的文章。 - legends2k

7

不可以。私有访问控制是为了防止您做蠢事,而不是作为防止他人访问您的数据或函数的安全机制。有很多方法可以规避它。


5

我尝试过。这是我编写的一个程序的nm,其中包含一个名为Test的类,其中有一个私有方法和一个公共方法。

0000000100000dcc T __ZN4Test3barEv
0000000100000daa T __ZN4Test3fooEv

正如您所看到的,签名完全相同。链接器无法区分私有方法和公共方法。


是的,正如我提到的,在 G++ 中访问 actualPrintX() 没有任何错误;但是 VC++ 中的符号似乎有所不同,因此链接器抛出了一个错误。谢谢! - legends2k

5

我同意其他答案的观点。

然而,我想指出,当你去掉private时,编译器可以在物理上以不同方式排列成员,这是完全可以接受的。如果能够正常工作,那就是运气。你不能依赖它。如果两边没有使用相同的声明,那么它们实际上并没有使用相同的类。


2

由于没有人提到如何阻止这种情况... 一种可能的阻止私有成员访问的方法是将它们声明为单独的内部类型,在文件外不可见。当然,如果您想要显式访问此内部类型,则必须提供内部声明。通常使用包含该类型的内部头文件来完成这个任务。

注意:在这个例子中,您需要跟踪分配/释放该内部对象。还有其他不需要这样做的方法。

// class_A.h
class A {
public:
  void printX();
private:
  void *Private;
};

// class_A.cpp
class A_private {
  void actualPrintX();
  int x;
};

void A::printX() {
  reinterpret_cast<A_private *>(Private)->actualPrintX();
}

void A_private::actualPrintX() {
  std::cout << x:
}

同样,在评论中,我也提到了切尔夫猫方法,它与此类似,只是没有reinterpret_cast。 - legends2k
对不起,我可能错过了那个;知道它实际上有一个名字很好。 - Ioan

2

没有任何地方实现了A::actualPrintX。这就是你的链接错误。


谢谢通知,这是一个打字错误,我已经修复了 :) - legends2k
各位,请不要给他投反对票。在问题早期,我只是写了 actualPrintX() 而不是 A::actualPrintX(),他只是通知我这个笔误而已。 - legends2k

1
在大多数情况下,您甚至不需要编辑头文件来使私有成员公开。您可以使用预处理器来完成此操作。类似于这样的代码:
//"classWithPrivateMembers.hpp"
class C
{
private: //this is crucial for this method to work
    static int m;
};

int C::m = 12;

然后,这将起作用:

#define private public  
#include "classWithPrivateMembers.hpp"  
#undef private

int main()
{
    C::m = 34; // it works!
}

您还可以添加 #define class struct 来捕获未经限定的私有变量。 - mskfisher

1
还要记住,当您更改成员变量的访问权限时,编译器可能会将其放置在类对象的不同偏移量上。标准允许编译器在重新排列成员方面有相当大的自由度(至少在相同访问级别内)。

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