为什么会发生这种情况?

3

给定以下代码:

class TestA
{
    private:
        char Temp;

    public:
        char *Ptr;

        TestA(){Ptr = NULL; Temp = 'A'; Ptr = &Temp;}
        void Function(){Ptr = &Temp; Temp = 'B';}

        void operator=(const TestA &ItemCopy)
        {
            //ItemCopy.Temp = 'N'; //Not permitted
            printf("%c!\n",ItemCopy.Temp);
            Ptr = ItemCopy.Ptr; //This is okay
            *Ptr = 'M'; //This is okay, but it re-assigns ItemCopy.Temp. What?
            printf("%c!\n",ItemCopy.Temp);
        }
};

int main()
{
    TestA Temp1,Temp2;

    Temp1.Function();
    Temp2 = Temp1;
}

生成以下内容:

B
M

即使ItemCopy是const的,为什么我可以间接地修改它甚至获取指针的非const副本?


我认为这是未定义行为。 - Karel Petranek
1
在C++中有一些规则更像是建议,如果你愿意,你可以绕过它们 - 无论好坏。 - AndersK
2
@dark_charlie:你为什么认为这是“未定义行为”?(我不这么认为,但你可能发现了我没有注意到的东西。) - CB Bailey
2
@Anders K.:不完全正确。它们更像是路标,告诉你道路的尽头在哪里。离开道路并不是非法的,只是意味着你不再在道路提供的限制和保证范围内操作(例如“你不会突然开到悬崖边”,或者“你前面不会突然出现树木”)。违反C++的规则,你就走了弯路。这不是“非法”的,只是你的程序不再是C++,你不能假设任何部分的程序仍然会像C++一样运行。 - jalf
jalf:这个比喻真的很棒。你的评论应该被维基化或者传承给后代 :) - rubenvb
5个回答

9

由于ItemCopy是const类型,ItemCopy.Ptr的有效类型为char * const。指针是const类型的,但所指向的项可以被修改。这意味着赋值操作:

*ItemCopy.Ptr = 'M';

如果含义明确且允许(底层对象本身不是const),则复制指针并通过它进行赋值也是合法的,就像您所做的那样。直接赋值ItemCopy.Temp = 'M'是不合法的,但这并不意味着您不能修改变量ItemCopy.Temp,只要有另一个非const的访问路径即可。


有没有办法在赋值时将其变为const char * const? - SE Does Not Like Dissent
@SSight3:你可以将成员变量设置为const char*,但我认为你的示例是人为演示。你想实现特定的目标吗?我认为没有充分的理由让一个成员变量指向同一类实例中的另一个成员变量,并且将其设置为公共的几乎肯定是一个糟糕的设计。 - CB Bailey
这是人工的。在构建适当的类之前,我正在测试假设,并且只想确保我知道尽可能多的选项。 - SE Does Not Like Dissent

2

指针别名问题。通过将 ItemCopy::Ptr 赋值给 this->Ptr,您会创建指针别名,并可以通过它为另一个值赋值。在编写此类代码时,请记住 三法则


1
提供三法则的链接会很有帮助。 - Kurt Stutsman
请注意,这只是一个示例类,用于演示发生的情况。 Function() 使得 Temp1 和 Temp2 有不同的状态。 - SE Does Not Like Dissent

2

Ptr指向ItemCopy.Ptr,而ItemCopy.Ptr又指向Temp。因此,当您对其进行解引用时,将写入Temp


1

关于语义:

    const TestA &ItemCopy

只能保证指针成员ItemCopy.Temp本身不能被直接修改,指针所指向的内容并不保证是const。


0

这是因为const规则是在编译时应用的,但这种规避是运行时状态的结果。对于常量引用对象的成员进行非常量拷贝不会修改该对象,因此不会违反常量引用。这是因为就编译器而言,你所做的一切只是复制一个值、一个内存块的地址。它并不预测你可能在这里或其他地方对其进行解引用——它不会将const应用于内存位置,而是应用于标识符,而你还没有违反这一点。

在运行时,你将非常量指针分配给一个也恰好被常量标识符引用的地址,但两者之间没有编译时连接,因为const仅适用于其中一种访问方式,甚至仅适用于此函数作用域内。它不能根据运行时状态在某些情况下将你的非常量指针视为常量,在其他方面违反C++的语义规则。


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