一文让你彻底明白C 中的const

在抽象的最高层次上,const做两件事:

  • 一种保护你自己的方式(类似于private)

  • 对编译器的一种指示,表明标记为const的对象适合于程序的数据段。换句话说,属于只读数据(ROM-able)。

可以通过例子来看下const的应用。第一个例子中,使用const覆盖了整个例子:

  1. void fun(int i, std::string const & str)

  2. {

  3. i = 0; //ok.

  4. str = ''; //error!

  5. int const n = 42;

  6. n = 2; //error!

  7. }

第二种情况只适用于静态初始化的名称空间-作用域变量(又称全局变量):

  1. int const pi = 3; //ROM-able

  2. std::vector<int> const ivec = {/* ... */}; //Not ROM-able, might allocate.

对声明为const的变量的任何写操作都被显示为未定义行为。这支持const全局变量在ROM中的位置。如果一个变量定义在ROM中,对它的写操作很可能会使程序崩溃,这取决于平台。如果一个变量不在ROM中,对它的写操作只会改变它的值。这两种情况的结合就是为什么对const变量执行写操作的行为是未定义行为而不是错误。

如果真的需要改写一个const变量的值,可以通过 const_cast来改写:

  1. void fun(int i, std::string const & str)

  2. {

  3. i = 0; //ok.

  4. const_cast<std::string &>(str) = ''; //Also ok (maybe).

  5. }

然而,const_cast并不能避免你在尝试写入声明为const的变量时永远不会遭遇未定义行为的陷阱。

  1. std::string str = '';

  2. fun(0, str); // Ok.

  3. std::string const const_str = '';

  4. 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

为了优化目的,编译器通常不能使用一致性进行优化。

  1. int get_value(some_class const & x, int const at)

  2. {

  3. int offset = compute_offset(at);

  4. return x[offset];

  5. }

此时,在这些函数参数中使用const对优化器没有帮助。x上的const不起作用,因为x已经通过引用传递了。没有x的副本,编译器不知道x是否声明为const。在参数at上的const不能帮助我们,因为at是一个拷贝,它可以以任何方式装入寄存器。如果编译器可以看到const对象的声明,它有时可以使用其一致性进行优化。

  1. std::vector<int> const vec = { 1, 2, 3 };

  2. int main()

  3. {

  4. // This may generate code that indexes into vec, or it may generate

  5. // code that loads an immediate 2.

  6. return vec[1];

  7. }

如果您想保证这样的优化,您可以在c++11或以后的版本中使用constexpr。使用constexpr声明的变量只对可以静态初始化的类型进行编译,因此,如果编译了它,就会得到一个ROM-able的对象。

  1. constexpr std::array<int, 3> arr = { 1, 2, 3 };

  2. int main()

  3. {

  4. // This generates code that loads an immediate 2 on every

  5. // compiler I tried.

  6. return arr[1];

  7. }

constexpr只处理字面常量类型(literal types)。这些类型与你可能在C中找到的类型相似。任何被声明为const的int或其他整数值都可以像文字一样使用。

  1. void foo(int const arg)

  2. {

  3. int const size = 2;

  4. int array_0[2]; // Ok.

  5. int array_1[arg]; // Error! arg is a runtime value.

  6. int array_2[size]; // Ok.

  7. }

静态const类成员

如果声明一个类成员static const,就很像声明一个全局const变量。

  1. int const global_size = 3;

  2. struct my_struct

  3. {

  4. static int const size = 2;

  5. };

  6. std::array<int, global_size> make_global_array()

  7. { return {}; }

  8. std::array<int, my_struct::size> make_my_struct_array()

  9. { return {}; }

但因为这是c++,所以有个问题。如果定义的static const整数值超越界限,则它可能无法被当作字面常量使用,例如一下的例子。

  1. struct my_struct

  2. {

  3. static int const size;

  4. };

  5. std::array<int, my_struct::size> make_my_struct_array() // Error!

  6. { return {}; }

  7. int const my_struct::size = 2;

这是的错误时因为编译器在解析foo()时不知道要为my_class::size使用什么值。如果你希望像使用全局const整数值一样使用static const整数值,请始终将它们声明为内联(inline)。

总结

  1. Use const to protect yourself from silly mistakes, and use it everywhere.
  2. You can use const on globals that you want in ROM, but prefer constexpr.
  3. Use const_cast sparingly, or not at all.
  4. When you do use const_cast, be careful of the UB that might result, including crashes.
  5. Use const globals and stack variables instead of macros for named values that are 'as good as literals'.
  6. Define static const integral data members inline, if possible.

本文作者:focuscode,16年控制工程毕业,不慎误入C/C++怀抱,希望有一天可以给简历的精通C++加上双引号~。

(0)

相关推荐