C++、类、常量和奇怪的语法

C++, Classes, Const, and strange syntax

本文关键字:语法 常量 C++      更新时间:2023-10-16

我今天正在重新阅读 c++ 入门(第 4 版( - 关于成员函数和常量引用等的部分,我想出了这个奇怪的小程序:

using std::cout;
using std::endl;
class ConstCheater
{
public:
    ConstCheater(int avalue) : ccp(this), value(avalue) {}
    ConstCheater& getccp() const {return *ccp;}
    int value;
private:
    ConstCheater* ccp;
};
int main()
{
    const ConstCheater cc(7); //Initialize the value to 7
    cout << cc.value << endl;
    cc.getccp().value = 4;    //Now setting it to 4, even though it's const!
    cout << cc.value << endl;
    cc.value = 4;             //This is illegal
    return 0;
}

我的问题是 - 为什么 c++ 允许这样的语法?为什么当类声明为 const 时,我可以编辑类中的普通数据成员?常量点不是为了让你无法修改值吗?

即使getccp()是一种const方法,它也不会承诺如何处理它返回的引用。该方法本身不会修改对象,因此不会破坏规则。

如果它返回一个const ConstCheater&那么情况会有所不同。

如您的示例所示,const比仅将其应用于对象要复杂得多。 C++常见问题解答有一个关于常量正确性的部分,特别是它涵盖了您在此处强调的情况。

我会说托尼你标记为正确的答案是不正确的,而迈克尔伯尔的答案是正确的。

把他说的话说得更清楚(至少对我而言(:

有两个

潜在的误解地方:

  1. 隐式常量的工作方式
  2. 构造 const 对象期间解释this的方式

1. 隐式常量

隐式常量(当它被制造constConstCheater内部的常量(不会把cc变成一个pointer-to-const,而是一个const-pointer,也就是说,当你这样做时:

  const ConstCheater cc(7); 

内部从:

  ConstCheater * ccp;

。自。。。

  ConstCheater * const ccp;

。而不是...

  const ConstCheater * ccp;    

这可能是意料之中的。

2. const对象的构建

更奇怪的是,this被允许传递给构造函数中的cpp初始值设定项,因为this,人们会认为,应该被视为一个pointer-to-const,因此不是一个传递给const-pointer的有效值。

也就是说,人们可能会期望:

 ...: ccp(this) ... // expected to fail but doesnt

失败是因为从概念上讲,您可能会期望这(在某种程度上(等同于:

 const ConstCheater         cc(7);
 const ConstCheater * const this = &cc; // const-pointer-to-const

因此,您会认为:

 ConstCheater * const ccp = this; //expected error!

会失败!但事实并非如此,因为显然在施工过程中,显然this被特别对待,就好像它是:

 const ConstCheater * this = &cc; 

因此,在施工过程中,对象实际上不是常量。

不确定我是否完全理解了其中的推理,但迈克尔·伯尔指出,提供预期行为似乎存在逻辑和技术障碍,因此该标准似乎开辟了当前有些奇怪的行为。

我最近问了一个相关的问题:为什么C++没有 const 构造函数? 但到目前为止还没有真正完全理解为什么它是站不住脚的,尽管我认为这会给C++开发人员带来负担,因为他们必须为他们想要创建 const 对象的任何类定义一个笨拙的 const 构造函数。

允许构造函数修改const对象的值,是的。但如果不是这样,它能做什么呢?

由于构造函数具有此类访问权限,因此它可以将其"转发"给其他人或"保存"它以供以后使用。当然,这样做可能是一个坏主意。

这是C++的安全机制不会阻止您构建格式错误的程序的一个实例。C++绝不是万无一失的。所以,要小心!

对象在构造函数完成其操作之前不会变为 const。因此,当您存储它时,this是指向非常量内存的指针,但不久之后会更改。这就是为什么它首先允许分配,因为你没有做错任何事。这意味着cpp是指向 const 的指针,但编译器没有意识到这一点。它没有办法;毕竟,您宣布它不是常量。这仍然是未定义的行为,它不是编译器真正希望帮助您捕获的类型。

真正的问题不是ConstCheater::getccp()的行为 - 而是行上没有错误:

const ConstCheater cc(7);

它使用应该是常量this指针初始化非常量指针。 但是,构造函数不能const(9.3.2/5,但稍微思考一下应该会明白原因(。 因此,允许构造函数使用指向 const 对象(或"即将成为"const 的对象(的指针初始化非 const 指针。不过,这就是你正在打的洞。

至于为什么允许这样做,我想标准很难尝试关闭漏洞,因为它必须枚举构造函数this必须const处理的所有方式,以及在构造 const 对象时必须non-const处理的所有方式。 这似乎是一项相当艰巨的任务。

你的对象不是完全不可变的:它只是你创建的指向它的引用是常量。

你正在做的const是对ConstCheater的引用。ConstCheater中没有任何东西使value成为const.

const限定符限制在对象上调用非常量方法,因此问题在于您的设计允许您通过常量方法对成员进行非常量引用。常见的方法是

      Member& getccp()       {return *member;}
const Member& getccp() const {return *member;} 

在某些情况下,当对象的逻辑恒定性不会受到其成员的外部修改的影响时,您可以允许

      Member& getccp() const {return *member;}

逻辑常性和形式恒常性差异的另一个例子是mutable成员。 即可变可以是在上一个const方法调用时计算的一些术语,如果你在下次调用时得到相同的输入,你可以很容易地返回存储的值。