C++、类、常量和奇怪的语法
C++, Classes, Const, and strange syntax
我今天正在重新阅读 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++常见问题解答有一个关于常量正确性的部分,特别是它涵盖了您在此处强调的情况。
我会说托尼你标记为正确的答案是不正确的,而迈克尔伯尔的答案是正确的。
把他说的话说得更清楚(至少对我而言(:
有两个潜在的误解地方:
- 隐式常量的工作方式 在
- 构造 const 对象期间解释
this
的方式
1. 隐式常量
隐式常量(当它被制造const
时ConstCheater
内部的常量(不会把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
方法调用时计算的一些术语,如果你在下次调用时得到相同的输入,你可以很容易地返回存储的值。
- 1d 智能指针不适用于语法 (*)++
- #定义c-预处理器常量..我做错了什么
- 用C++中的一个变量定义一个常量
- 什么时候在C++中返回常量引用是个好主意
- 助记符和指向成员语法的指针
- 有人能分解一下这个c++模板的语法吗
- C++避免重复声明的语法是什么
- 代理对象的常量正确性
- 我想将一个对T类型的非常量左值引用绑定到一个T类型的临时值
- QMetaObject invokeMethod的基于函数指针的语法
- 通过多个头文件使用常量变量
- 在cuda线程之间共享大量常量数据
- 不能在初始值设定项列表中将非常量表达式从类型 'int' 缩小到'unsigned long long'
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用
- 常量参考的正确语法
- 语法错误"常量错误"
- 如何避免语法相同的常量和非常量函数之间代码重复,这些函数在语义上不相同
- C2059:语法错误:常量
- 将值赋给常量的语法或语义错误
- C++、类、常量和奇怪的语法