考虑以下内容:
int i = 3
i
是一个对象,它的类型为int
。它没有被限定(不是const
或者volatile
,或者两者都不是)。
现在我们添加:
const int& j = i;
const int* k = &i;
j
是指向i
的引用,而k
是指向i
的指针。(从现在开始,我们将“引用”和“指向”简称为“指向”。)
此时,我们有两个cv-qualified变量j
和k
,它们指向一个非cv-qualified对象。这在§7.1.5.1/3中提到:
指向或引用cv-qualified类型的指针或引用不一定实际指向或引用cv-qualified对象,但它被视为实际指向或引用;即使所引用的对象是非const对象并且可以通过其他访问路径进行修改,也不能使用const限定的访问路径来修改对象。[注意:类型系统支持cv-qualifier以便它们不能被转换(5.2.11)]
这意味着编译器必须尊重j
和k
是cv-qualified的,即使它们指向一个非cv-qualified对象。(因此j = 5
和*k = 5
是不合法的,即使i = 5
是合法的。)
现在考虑将其移除const
:
const_cast<int&>(j) = 5;
*const_cast<int*>(k) = 5;
这是合法的(§参见5.2.11),但它是否未定义行为?不是。请参见§7.1.5.1/4:
除了任何声明为mutable(7.1.1)的类成员可以被修改,在其生命周期(3.8)内尝试修改const对象会导致未定义的行为。强调我的。
请记住,i
不是const
,而j
和k
都指向i
。我们所做的只是告诉类型系统从类型中删除const限定符,以便我们可以修改指向的对象,然后通过这些变量修改i
。
这与执行以下操作完全相同:
int& j = i; // removed const with const_cast...
int* k = &i; // ..trivially legal code
j = 5;
*k = 5;
而这个过程是非常合法的。我们现在考虑 i
改为如下:
const int i = 3;
我们现在的代码怎么样了?
const_cast<int&>(j) = 5;
*const_cast<int*>(k) = 5;
现在会导致未定义的行为,因为i
是一个带有const限定词的对象。我们告诉类型系统删除const
以便可以修改指向的对象,然后修改了一个带有const限定词的对象。如上所述,这是未定义的。
再次强调,更明显的是:
int& j = i; // removed const with const_cast...
int* k = &i; // ...but this is not legal!
j = 5;
*k = 5;
请注意,仅仅这样做是不够的:
const_cast<int&>(j);
*const_cast<int*>(k);
这是完全合法和明确定义的,因为没有修改const-限定的对象;我们只是在玩弄类型系统。
现在考虑:
struct foo
{
foo() :
me(this), self(*this), i(3)
{}
void bar() const
{
me->i = 5;
self.i = 5;
}
foo* me;
foo& self;
int i;
};
bar
中的const
对成员变量有什么影响?它使得访问成员变量必须通过一条称为cv-qualified access path的路径。(它通过将this
的类型从T* const
更改为cv T const*
来实现,其中cv
是函数的cv限定符。)
那么在执行bar
期间成员变量的类型是什么?它们是:
// const-pointer-to-non-const, where the pointer points cannot be changed
foo* const me;
// foo& const is ill-formed, cv-qualifiers do nothing to reference types
foo& self;
// same as const int
int const i;
当然,指针类型并不重要,重要的是指向对象的 const 限定,而不是指针本身。如果上面的 k 是 "const int* const",那么后面的 const 就无关紧要了。现在我们考虑:
int main()
{
foo f;
f.bar();
}
在 bar
中,me
和 self
都指向一个非常量的 foo
,所以和上面的 int i
一样,我们有明确定义的行为。如果我们有:
const foo f;
f.bar(); // UB!
我们会遇到UB,就像使用const int
一样,因为我们要修改一个带有const修饰的对象。
在你的问题中,没有带有const修饰的对象,所以没有未定义的行为。
为了补充权威性,考虑Scott Meyers的const_cast
技巧,用于在非const函数中重复使用一个带有const修饰的函数:
struct foo
{
const int& bar() const
{
int* result =
return *result;
}
int& bar()
{
}
};
他建议:
int& bar(void)
{
const foo& self = *this;
const int& result = self.bar();
return const_cast<int&>(result);
}
或者通常的写法:
int& bar(void)
{
return const_cast<int&>(
static_cast<const foo&>(*this)
.bar()
);
}
请注意,这是完全合法和明确定义的。具体来说,因为必须在非const限定的
foo
上调用此函数,所以我们可以完全安全地从
int& boo() const
的返回类型中去除const限定。
(除非有人一开始就使用
const_cast
+ 调用自己。)
总结一下:
struct foo
{
foo(void) :
i(),
self(*this), me(this),
self_2(*this), me_2(this)
{}
const int& bar() const
{
return i;
}
int& bar() const
{
return const_cast<int&>(
static_cast<const foo&>(*this).
bar()
);
}
void baz() const
{
i = 5;
me = 0;
me_2 = 0;
self.i = 5;
me->i = 5;
self_2.i = 5;
me_2->i = 5;
int r = const_cast<foo&>(self_2).i;
r = const_cast<foo* const>(me_2)->i;
const_cast<foo&>(self_2);
const_cast<foo* const>(me_2);
const_cast<foo&>(self_2).i = 5;
const_cast<foo* const>(me_2)->i = 5;
const_cast<foo&>(*this).i = 5;
const_cast<foo* const>(this)->i = 5;
}
int i;
foo& self;
foo* me;
const foo& self_2;
const foo* me_2;
};
int main()
{
int i = 0;
{
int& x = i;
int* y = &i;
const int& z = i;
const int* w = &i;
const_cast<int&>(z) = 5;
const_cast<int*>(w) = 5;
}
const int j = 0;
{
int& x = j;
int* y = &j;
const int& z = i;
const int* w = &i;
const_cast<int&>(z) = 5;
const_cast<int*>(w) = 5;
}
foo x;
x.bar();
x.bar() = 5;
x.baz();
const foo y;
y.bar();
const_cast<foo&>(y).bar();
const_cast<foo&>(y).bar() = 5;
y.baz();
}
我参考的是 ISO C++03 标准。