为什么C ++允许私有成员使用此方法进行修改?(Why does C++ allow private

2019-06-27 07:04发布

在看到这个问题,几分钟前,我不知道为什么语言设计者允许它,因为它允许私有数据的间接修改。 举个例子

 class TestClass {
   private:
    int cc;
   public:
     TestClass(int i) : cc(i) {};
 };

 TestClass cc(5);
 int* pp = (int*)&cc;
 *pp = 70;             // private member has been modified

我测试了上面的代码确实私有数据已被修改。 有没有为什么,这是不允许发生任何解释或者这只是在语言的监督? 这似乎直接削弱使用私有数据成员。

Answer 1:

因为,比亚所说的那样,C ++的设计,以防止墨菲,不马基雅维里。

换句话说,它应该保护你免受意外 - 但如果你去到任何工作,要颠覆它(比如使用CAST)它甚至没有去试图阻止你。

当我想起来了,我心里有一个稍微不同的比喻:这就像在浴室的门上的锁。 它给你,你可能不希望走在那里,现在一个警告,但它是平凡的,如果你决定从外面打开门。

编辑:至于问题@Xeo讨论,关于为什么标准说“具有相同的访问控制”,而不是“有所有的公共访问控制”,答案很长,有点曲折。

让我们回过头来开始和考虑像一个结构:

struct X {
    int a;
    int b;
};

C时,同时有一些规则,这样的结构。 一个是在结构的实例,该结构本身的地址必须等于地址a ,这样你就可以投一个指向结构的指针int ,并获得a具有明确的结果。 另一个原因是,各成员具有如它们在结构定义(尽管编译器可以自由插入在它们之间填充)将被布置在存储器中的顺序相同。

对于C ++,有保持的是,尤其是对于现有C结构的意图。 与此同时,有一个明显的意图是,如果编译器要执行private (和protected在运行时),它应该很容易做到这一点(合理有效)。

因此,鉴于这样的:

struct Y { 
    int a;
    int b;
private:
    int c;
    int d;
public:
    int e;

    // code to use `c` and `d` goes here.
};

编译器应该被要求维持相同的规则为C相对于YaYb 。 同时,如果它要执行在运行时访问,这可能需要所有的公共变​​量一起移动在内存中,这样的布局会更喜欢:

struct Z { 
    int a;
    int b;
    int e;
private:
    int c;
    int d;
    // code to use `c` and `d` goes here.
};

然后,当它在运行时执行的东西,它基本上可以这样做if (offset > 3 * sizeof(int)) access_violation();

据我所知,没有人做过这个,我不知道该标准的其他真的允许的话,但似乎一直是至少沿着这条线的想法的半形成的根源。

为了执行这两个中,C ++ 98所述Y::aY::b必须是在存储器的顺序,和Y::a必须是在该结构的开始时(即,C状规则)。 但是,由于中间的访问说明中, Y::cY::e不再需要在相互顺序。 换句话说,所有没有他们之间的访问说明符定义的连续变量组合在一起,编译器可以自由地重新排列这些组(但仍必须保持在一开始的第一个)。

这是罚款,直至一些挺举(即我)指出,规则编写方式有另一个小问题。 如果我写了这样的代码:

struct A { 
    int a;
public:
    int b;
public:
    int c;
public:
    int d;
};

...你结束了自我矛盾性的一点点。 一方面,这仍然是一个正式的POD结构,所以C类规则应适用 - 但因为你有成员之间(当然无意义)访问说明,它也给了编译器许可重新排列成员,从而打破了类似C的规则,他们打算。

为了治治,他们重新措辞的标准一点,所以它会谈论都具有相同的访问,而不是关于是否有他们之间的访问说明符的成员。 是的,他们可能刚刚颁布的规则将只适用于公众成员,但似乎没有人看到有什么可以从获得的。 由于这是修改与大量的代码已经使用了相当长的一段现有标准中,选择了他们能有这样仍然会治愈的问题最小化。



Answer 2:

由于向后兼容性与C,在那里你可以做同样的事情的。


对于所有的人都知道,这里的原因,这不是UB事实上是标准允许的:

首先, TestClass是一个标准的布局类§9 [class] p7 ):

一个标准的布局类是一类:

  • 有类型的非标准布局类(或这些类型的阵列)或参考,//行的没有非静态数据成员:非静态数据成员的类型为“INT”的
  • 没有虚函数(10.3),并且没有虚基类(10.1),// OK
  • 具有用于所有非静态数据成员相同的访问控制(第11),//行,所有的非静态数据成员(1)是“私人”
  • 有没有不规范,布局基类,// OK,没有基类
  • 或者具有在最派生类,并在与非静态数据成员最多有一个基类中没有非静态数据成员,或具有与非静态数据成员没有基类,和// OK,没有基类再次
  • 具有相同的类型与第一非静态数据成员的无基类。 // OK,没有基类再

有了这样的,就可以被允许reinterpret_cast类到它的第一部件的类型( §9.2 [class.mem] p20 ):

指向一个标准布局结构对象,使用适当转换reinterpret_cast ,点到它的初始成员(或如果该构件是一个位域,然后在它所在的单元),并且反之亦然。

在你的情况下,C-风格(int*)投解析到reinterpret_cast§5.4 [expr.cast] p4 )。



Answer 3:

一个很好的理由是允许使用C,但C ++层上额外获得安全的兼容性。

考虑:

struct S {
#ifdef __cplusplus
private:
#endif // __cplusplus
    int i, j;
#ifdef __cplusplus
public:
    int get_i() const { return i; }
    int get_j() const { return j; }
#endif // __cplusplus
};

通过要求C-可见S和C ++ -可见S布局兼容S可以跨语言边界被用于与具有较大访问安全的C ++侧。 该reinterpret_cast访问安全的颠覆是不幸的,但必然结果。

顺便说一句,在具有与所述相同的访问控制的所有成员的限制是因为实现被允许重新排列相对于与不同的访问控制部件成员。 据推测一些实现把部件使用相同的访问控制一起,为整洁起见; 它也可以用来减少填料,虽然我不知道这是否任何编译器。



Answer 4:

整个的目的reinterpret_cast (和C风格的转换甚至比一个更强大reinterpret_cast )是提供围绕安全措施逃生路径。



Answer 5:

编译器就会给你,如果你曾经尝试错误int *pp = &cc.cc ,编译器会告诉你,你不能访问私有成员。

在你的代码被重新诠释CC的地址为指针为int。 你写的空调风格方式,C ++风格方式本来int* pp = reinterpret_cast<int*>(&cc); 。 使用reinterpret_cast始终是你正在做的是不相关的两个指针之间的一个铸件的警告。 在这种情况下,你必须确保你正在做正确的。 你必须知道底层的内存(布局)。 编译器不会阻止你这样做,因为这一点,如果经常需要。

在做演员,你扔掉关于类的所有知识。 从现在开始,编译器只看到一个int指针。 当然你也可以访问内存的指针指向。 在你的情况,你的平台上编译器碰巧把CC在TestClass中对象的前n个字节,所以TestClass的指针也指出,CC成员。



Answer 6:

这是因为你操纵您的类位于内存的内存。 在你的情况下,它只是发生在私有成员存储在这个存储位置,这样你改变它。 这不是一个很好的主意,这样做,因为你现在知道物体将如何被存储在内存中。



文章来源: Why does C++ allow private members to be modified using this approach?