在抽象的最高层次上,const做两件事:
- 一种保护你自己的方式(类似于private)
- 对编译器的一种指示,表明标记为const的对象适合于程序的数据段。换句话说,属于只读数据(ROM-able)。
可以通过例子来看下const的应用。第一个例子中,使用const覆盖了整个例子:
void fun(int i, std::string const & str) { i = 0; //ok. str = ""; //error! int const n = 42; n = 2; //error! }
第二种情况只适用于静态初始化的名称空间-作用域变量(又称全局变量):
int const pi = 3; //ROM-able std::vector<int> const ivec = {/* ... */}; //Not ROM-able, might allocate.
对声明为const的变量的任何写操作都被显示为未定义行为。这支持const全局变量在ROM中的位置。如果一个变量定义在ROM中,对它的写操作很可能会使程序崩溃,这取决于平台。如果一个变量不在ROM中,对它的写操作只会改变它的值。这两种情况的结合就是为什么对const变量执行写操作的行为是未定义行为而不是错误。
如果真的需要改写一个const变量的值,可以通过 const_cast
来改写:
void fun(int i, std::string const & str) { i = 0; //ok. const_cast<std::string &>(str) = ""; //Also ok (maybe). }
然而,const_cast并不能避免你在尝试写入声明为const的变量时永远不会遭遇未定义行为的陷阱。
std::string str = ""; fun(0, str); // Ok. std::string const const_str = ""; fun(0, const_str); // Undefined Behavior!!
因此,只有在确实需要时才使用const_cast,并且只有在知道要写入的底层变量是如何声明的情况下才使用。
那么,究竟在什么时候什么地方使用const? 答案就是Everywhere。将每个变量声明为const,除非您知道它将被写入。更一般地,在编译器接受的任何地方添加const。
1. int foo(int arg) 2. { 3. int const x = compute_intermediate_result(arg); 4. int const y = compute_other_intermediate_result(x); 5. return something_computed_from(x, y); 6. }
优化器视角下的const
为了优化目的,编译器通常不能使用一致性进行优化。
int get_value(some_class const & x, int const at) { int offset = compute_offset(at); return x[offset]; }
此时,在这些函数参数中使用const对优化器没有帮助。x上的const不起作用,因为x已经通过引用传递了。没有x的副本,编译器不知道x是否声明为const。在参数at上的const不能帮助我们,因为at是一个拷贝,它可以以任何方式装入寄存器。如果编译器可以看到const对象的声明,它有时可以使用其一致性进行优化。
std::vector<int> const vec = { 1, 2, 3 }; int main() { // This may generate code that indexes into vec, or it may generate // code that loads an immediate 2. return vec[1]; }
如果您想保证这样的优化,您可以在c++11或以后的版本中使用constexpr。使用constexpr声明的变量只对可以静态初始化的类型进行编译,因此,如果编译了它,就会得到一个ROM-able的对象。
constexpr std::array<int, 3> arr = { 1, 2, 3 }; int main() { // This generates code that loads an immediate 2 on every // compiler I tried. return arr[1]; }
constexpr只处理字面常量类型(literal types)。这些类型与你可能在C中找到的类型相似。任何被声明为const的int或其他整数值都可以像文字一样使用。
void foo(int const arg) { int const size = 2; int array_0[2]; // Ok. int array_1[arg]; // Error! arg is a runtime value. int array_2[size]; // Ok. }
静态const类成员
如果声明一个类成员static const,就很像声明一个全局const变量。
int const global_size = 3; struct my_struct { static int const size = 2; }; std::array<int, global_size> make_global_array() { return {}; } std::array<int, my_struct::size> make_my_struct_array() { return {}; }
但因为这是c++,所以有个问题。如果定义的static const整数值超越界限,则它可能无法被当作字面常量使用,例如一下的例子。
struct my_struct { static int const size; }; std::array<int, my_struct::size> make_my_struct_array() // Error! { return {}; } int const my_struct::size = 2;
这是的错误时因为编译器在解析foo()时不知道要为my_class::size使用什么值。如果你希望像使用全局const整数值一样使用static const整数值,请始终将它们声明为内联(inline)。
总结
- Use const to protect yourself from silly mistakes, and use it everywhere.
- You can use const on globals that you want in ROM, but prefer constexpr.
- Use const_cast sparingly, or not at all.
- When you do use const_cast, be careful of the UB that might result, including crashes.
- Use const globals and stack variables instead of macros for named values that are "as good as literals".
- Define static const integral data members inline, if possible.
本文作者:focuscode,16年控制工程毕业,不慎误入C/C++怀抱,希望有一天可以给简历的精通C++加上双引号~。
声明:本文为 脚本之家专栏作者 投稿,未经允许请勿转载。