我正在学习Bjarne Stroustrup的"C++编程语言",他讲的是类的逻辑和物理常量.
逻辑constness的例子是这样的:
class A {
int m;
void func() const { m++; } //forbidden
}
Run Code Online (Sandbox Code Playgroud)
可以通过演员来绕过这个,例如:
class A {
int m;
void func() const { (A*) this)->m++; } //allowed
}
Run Code Online (Sandbox Code Playgroud)
用他的话说,逻辑常数是
"一个对用户来说似乎不变的对象."
和物理常数是
"存储在只读存储器中"
作为一个说明,他说
物理常量可以通过仅在没有构造函数的类中将对象放置在只读存储器中来实施
我不太明白这个说法.有人可以解释如何强制执行物理常量以及如果类有构造函数它不起作用的原因吗?
AnT*_*AnT 13
你已经收到了几个答案,但我相信他们中的大多数(如果不是全部的话)都会忽略这一点.
为了更好地理解C++中constness的情况(并且还涉及其他答案中提到的概念),让我们考虑不是两个,而是在C++程序中可能遇到的三个可能的constness级别
硬件/ OS级物理常量是其他答案似乎在描述的物理常量.当对象被放置在内存中而不被写入时会发生:只读(RO)内存.保护可以通过硬件提供的手段或OS提供的手段或两者来实现.但是,C++语言本身并没有将这种常量分成一个独特的类别.C++语言并不关心这些低级问题.当物理常量的概念出现在C++上下文中时,它通常指的是下一种常量.
语言水平的物理常数.当您将对象声明为时,此constness就会发生const.从C++语言的角度来看,以下对象是物理常量
const double d = 5;
const int i = 42;
const std::string str = "Hello World!";
const MyClass c;
Run Code Online (Sandbox Code Playgroud)
请注意,这些对象是否真的放在RO内存中并不重要.该语言表示,无论内存是否为RO,任何修改这些对象的尝试都将导致未定义行为(UB).另请注意,如果您尝试修改这些对象,则该UB的表现形式不仅限于程序崩溃(如果它们确实在RO内存中).例如,编译器可以自由地假设这些对象永远不会改变并且可以在该假设下优化代码,从而在看起来不必要的情况下消除对这些对象的访问.因此,即使这些对象占用的内存是可写的,即使您设法以某种方式"成功"修改它们,您的代码仍可能表现得好像您的修改从未发生过.UB是UB.任何事情都可能发生.
从语言的角度来看,这种常量当然是包括第一种.
最后,对逻辑constness.C++中的逻辑常量是所谓的对象访问路径的常量.访问路径是允许您间接访问现有对象的引用或指针.考虑这个声明
const MyClass* pc;
Run Code Online (Sandbox Code Playgroud)
这是一个指向const MyClass类型的指针.但请注意:它并不意味着此指针指向的实际对象是常量.它只是让你"看到"它作为一个常数.该对象可以很容易地成为常量
const MyClass c;
pc = &c;
Run Code Online (Sandbox Code Playgroud)
或者它可能是非常数
MyClass nc;
pc = &nc;
Run Code Online (Sandbox Code Playgroud)
换句话说,只有那个指针,p你有一个常量的路径指向某个类型的对象MyClass.您不知道并且(通常)不需要知道该对象是否真的是常量.由于给出的访问路径是常量,因此必须将该对象视为常量.当然,如果您以某种方式知道该访问路径另一端的对象不是常量,您可以合法地抛弃访问路径的常量
MyClass* p = const_cast<MyClass*>(pc);
Run Code Online (Sandbox Code Playgroud)
并对对象执行修改操作(当然,一般情况下,它不是一个好的编程实践,但它有其有效的用途).如果路径另一端的对象毕竟是一个常量,由于上述原因,行为将是未定义的.
请注意,原始帖子中的示例正是在谈论这一点.当声明类的方法A作为const,它只是意味着隐含this传递给方法的参数将具有类型const A*,即,其将提供给一个恒定访问路径A的对象.这就是我上面描述的逻辑常量.再次注意,如果对象被声明为,例如,const A a;它是语言级物理常量,并且如示例中所示修改它是非法的,无论对象是否驻留在RO存储器中.
现在,为了提供上述的最后一个例子,请考虑以下声明
const MyClass* const* const* const p = /* whatever */;
Run Code Online (Sandbox Code Playgroud)
该声明中有4个const限定词.其中一个资格赛与其他资格赛有着重大的质的差异.这是最右边的一个.最右边const声明了对象的物理常量p(指针本身的常量),而其余的const限定符声明了它们将指向的对象的逻辑常量(访问路径的常量).
同样,我相信在他的书中Stroustrup意味着谈论const的第二和第三个概念之间的区别,而不是第一个,因为C++语言并没有真正将第一和第二分开.注意,该示例表示通过转换constness进行修改是"允许的",而语言规范明确指出通过这种方法修改第二类常量是立即非法的.
构造函数作为构造过程的一部分写入对象,但如果对象的内存是只读的,则这不起作用.
物理const仅适用于可在编译时初始化的对象,因此不需要构造函数.
创建只读内存不是你可以在C++中明确做的事情,但是许多编译器都有扩展允许你这样做,并且大多数现代编译器会将代码和一些数据放在只读内存中(如果CPU架构允许的话).
在MSVC中,您可以将初始化的全局变量强制存储在只读内存中,方法是将其放在标记为读取的部分中,但不要像这样写入
#pragma section("rosec",read)
__declspec(allocate("rosec")) int j = 0; // this will be in a readonly data segment.
Run Code Online (Sandbox Code Playgroud)