AFAIK从const变量中删除constness是未定义的行为:
const int i = 13;
const_cast<int&>(i) = 42; //UB
std::cout << i << std::endl; //out: 13
但是const函数参数是“实”常量吗?让我们考虑以下示例:
void foo(const int k){
const_cast<int&>(k) = 42; //UB?
std::cout << k << std::endl;
}
int main(){
foo(13); //out: 42
}
似乎编译器对const int k
的优化与对const int i
的优化不同。
第二个示例中是否有UB? const int k
是帮助编译器优化代码还是编译器仅检查const正确性而已?
答案 0 :(得分:5)
i
中的const int i = 13;
可用作常量表达式(用作模板参数或大小写标签),并且尝试对其进行修改是未定义的行为。这样做是为了与没有constexpr
的C ++ 11以前的代码向后兼容。
声明void foo(const int k);
和void foo(int k);
声明了相同的功能;参数的顶级const
不参与函数的签名。参数k
必须按值传递,因此不能为“实数”常量。抛弃其常数并不是一种不确定的行为。 编辑:但由于它是const对象[basic.type.qualifier](1.1),因此仍未定义任何修改尝试:
const对象是const T类型的对象或此类对象的不可更改子对象。
通过[dcl.type.cv] 4个const对象无法修改:
除了可以声明任何声明为可变的类成员(10.1.1)之外,任何在其生存期(6.8)内修改const对象的尝试都会导致未定义的行为。
由于功能参数具有自动存储期限,因此[basic.life] 10也无法在其存储中创建新对象:
在具有静态,线程或自动存储持续时间的const完整对象所占用的存储中,或在此类const对象在其生命周期结束前曾经被占用的存储中创建新对象会导致不确定的行为。
>
目前尚不清楚,如果有计划抛弃其常数性,为什么k
首先被宣布为const
?感觉它的唯一目的是混淆和混淆。
通常,我们应该在任何地方都支持不变性,因为它可以帮助人们推理。它还可以帮助编译器进行优化。但是,在我们仅声明不可变性但不兑现它的情况下,它的作用相反且令人困惑。
我们应该赞成的其他事情是纯函数。这些不依赖于或修改任何外部状态,并且没有副作用。这些对于人和编译器来说也更容易推理和优化。当前,此类函数可以声明为constexpr
。然而,即使在const
函数的上下文中,将按值参数声明为constexpr
也无助于我的知识优化。
答案 1 :(得分:4)
但是const函数参数是“实”常量吗?
我认为答案是肯定的。
它们不会存储在ROM中(例如),因此丢弃const至少会出现 才能正常工作。但是我对标准的理解是参数的类型为const int
,因此它是 const对象([basic.type.qualifier]),因此修改它是未定义的([ dcl.type.cv])。
您可以相当容易地确认参数的类型:
static_assert( std::is_same_v<const int, decltype(k)> );
第二个示例中有UB吗?
是的,我想有。
const int k是否可以帮助编译器优化代码,或者编译器仅检查const的正确性而已?
理论上,编译器可以假设在以下示例中k
不会因调用g(const int&)
而改变,因为修改k
将是UB:
extern void g(const int&);
void f(const int k)
{
g(k);
return k;
}
但是实际上,我不认为编译器会利用它,而是假设k
可能会被修改(compiler explorer demo)。
答案 2 :(得分:0)
我不确定您所说的“真正的” const是什么意思,但是可以了。
您的const int i
是任何函数之外的变量声明。由于修改该变量将导致未定义的行为,因此编译器假定其值永远不会改变。一种简单的优化方法是,您从i
读取的任何地方,编译器都不必去从主内存中读取值,它可以发出程序集以直接使用该值。
您的const int k
(或更有趣的const int & k
)是一个函数参数。它所承诺的就是该函数不会更改k
的值。这并不意味着它不能在其他地方更改。函数的每次调用对于k
可以具有不同的值。