为什么C++允许使用这种方法修改私有成员?

mat*_*975 18 c++ private-members

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

 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
Run Code Online (Sandbox Code Playgroud)

我测试了上面的代码,实际上私有数据已被修改.有没有解释为什么允许这种情况发生,或者这只是对语言的疏忽?它似乎直接破坏了私人数据成员的使用.

Jer*_*fin 31

因为,正如Bjarne所说,C++旨在防范墨菲,而不是马基雅维利.

换句话说,它应该保护你免受意外伤害 - 但如果你去任何工作来颠覆它(比如使用演员),它甚至都不会试图阻止你.

当我想起它时,我会有一个不同的类比:它就像锁在浴室门上.它会给你一个警告,你现在可能不想在那里走路,但是如果你决定从外面解锁它是微不足道的.

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

让我们回到开头,考虑一个结构:

struct X {
    int a;
    int b;
};
Run Code Online (Sandbox Code Playgroud)

对于像这样的结构,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.
};
Run Code Online (Sandbox Code Playgroud)

编译器应该被要求维持相同的规则为C相对于Y.aY.b.同时,如果它将在运行时强制执行访问,它可能希望将所有公共变量一起移动到内存中,因此布局将更像:

struct Z { 
    int a;
    int b;
    int e;
private:
    int c;
    int d;
    // code to use `c` and `d` goes here.
};
Run Code Online (Sandbox Code Playgroud)

然后,当它在运行时强制执行时,它基本上可以执行类似的操作 if (offset > 3 * sizeof(int)) access_violation();

据我所知,没有人做过这个,我不确定标准的其余部分是否真的允许它,但似乎确实至少有一个半成形的细菌沿着那条线.

为了执行这两个中,C++ 98所述Y::aY::b必须是按该顺序在存储器中,并且Y::a必须是在结构体(即,C-像规则)的开始.但是,由于干预访问说明符,Y::c并且Y::e不再必须相互有序.换句话说,在它们之间没有访问说明符的情况下定义的所有连续变量被组合在一起,编译器可以自由地重新排列这些组(但仍然必须保留第一个开头).

这很好,直到一些混蛋(即我)指出规则的编写方式有另一个小问题.如果我写代码如下:

struct A { 
    int a;
public:
    int b;
public:
    int c;
public:
    int d;
};
Run Code Online (Sandbox Code Playgroud)

......你最终得到了一点点自我矛盾.一方面,这仍然是正式的POD结构,所以C类规则应该适用 - 但由于你在成员之间有(无可否认的无意义)访问说明符,它也赋予了编译器重新安排成员的权限,因此打破他们想要的C式规则.

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

  • @DeadMG:你认为这与我所说的任何内容相矛盾?我非常具体地*不*说结果是未定义的,或任何接近 - 只是你必须使用演员. (4认同)
  • @DeadMG:再一次,现在一起,数量为三:这里的保护是你必须使用演员表.是的,一旦你使用演员表,结果是明确定义的.你受到保护的事故就是在没有演员表的情况下写作作业*. (4认同)

Xeo*_*Xeo 14

由于与C的向后兼容性,你可以做同样的事情.


对于所有想知道的人来说,这就是为什么这不是UB并且实际上是标准所允许的:

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

一个标准布局类是一类:

  • 没有类型为非标准布局类(或此类型的数组)或引用的非静态数据成员,// OK:非静态数据成员的类型为"int"
  • 没有虚函数(10.3),也没有虚基类(10.1),// OK
  • 对所有非静态数据成员具有相同的访问控制(第11条),// OK,所有非静态数据成员(1)都是'私有'
  • 没有非标准布局基类,// OK,没有基类
  • 或者在大多数派生类中没有非静态数据成员,并且最多只有一个具有非静态数据成员的基类,或者没有带有非静态数据成员的基类,并且// OK,再次没有基类
  • 没有与第一个非静态数据成员相同类型的基类.//好的,再没有基类

有了它,你可以允许reinterpret_cast类到它的第一个成员(§9.2 [class.mem] p20)的类型:

指向标准布局结构对象的指针(适当地使用a转换)reinterpret_cast指向其初始成员(或者如果该成员是位字段,则指向它所驻留的单元),反之亦然.

在您的情况下,C样式转换(int*)解析为reinterpret_cast(§5.4 [expr.cast] p4).