请考虑以下代码段:
void foo(const int&);
int bar();
int test1()
{
int x = bar();
int y = x;
foo(x);
return x - y;
}
int test2()
{
const int x = bar();
const int y = x;
foo(x);
return x - y;
}
在我对标准的理解中,x
和y
都不允许在foo
中改变test2
,而foo
中的test1
可以改变它们(例如用const_cast
从const
中删除const int&
,因为引用的对象实际上不是const在test1
)。
现在,neither gcc nor clang nor MSVC似乎优化test2
到foo(bar()); return 0;
,我可以理解他们不想浪费优化传递优化,这在实践中很少适用。
但是,至少我对这种情况的理解是正确的,还是我错过了在x
修改test2
的合法途径?
该标准在[dcl.type.cv]中说:
除了可以修改声明
mutable
[...]的任何类成员之外,任何在其生命周期内修改const对象[...]的尝试都会导致未定义的行为。
根据[basic.life]的说法,也不可能通过过早地结束对象的生命周期来定义它:
在存储中创建一个具有自动存储持续时间的const完整对象占用的新对象,或者在这个const对象在其生命周期结束之前占用的存储内,会导致未定义的行为。
这意味着将x - y
优化为零是有效的,因为任何在x
中修改foo
的尝试都会导致未定义的行为。
有趣的问题是,是否有理由不在现有编译器中执行此优化。考虑到const对象定义是test2
的本地,并且事实在同一函数中使用,通常的例外,例如支持符号插入不适用于此处。
const
对于优化几乎没有任何帮助。编译器基本上需要一个关于代码的全局视图来决定变量是否真正是常量,并且无论const
修饰符如何,编译器都将确定这一点。考虑一下这段代码(例如在godbolt.org中)
void foo(const int& v) { const_cast<int&>(v) = 6; }
const int bar() { return 9; }
int main() {
const int a = bar();
const int b = a;
foo(a);
return a-b;
}
这将导致gcc8.3中的-O3和clang7达到非常正确和最佳(即使使用导致未定义行为的其他非必要的const_cast
):
foo(int const&): # @foo(int const&)
mov dword ptr [rdi], 6
ret
bar(): # @bar()
mov eax, 9
ret
main: # @main
mov eax, -3
ret
什么是重要的:如果你用const int
替换所有int
,这是相同的。因此,它不是帮助编译器的const,而是全局代码视图和分析。
最后让我引用Herb Sutter http://www.gotw.ca/gotw/081.htm的这个非常有趣的GotW页面,我看到它也开始了整个问题。
因此,const
仍然是程序员的主要特征。编译器比我们更聪明,无论如何都不信任我们;-)
最后一点:优化几乎在所有情况下都是编译器的一个特性,而不是语言的特性。这有一些例外,例如copy elision
,但这个问题不是其中之一。