C++ 中让人头晕的const & constexpr

在使用 C++ const 的时候,看到 const 这些用法脑袋都是晕的,如 const int*const int * constint const *。并且在 C++ 11加入了 constexpr 之后也不知道它和 const 有什么区别,这篇文章主要就是整理一下这方面的知识点。

const

const 一般的用法就是修饰变量、引用、指针,修饰之后它们就变成了常量,需要注意的是 const 并未区分出编译期常量和运行期常量,并且 const 只保证了运行时不直接被修改。

一般的情况,const 也就简单这么用一下,const 放在左边,表示常量:

const int x = 100; //常量
const int& rx = x; //常量引用
const int* px = &x;//常量指针

给变量加上 const 之后变量”就成了“常量”,只能读、禁止写,编译器会帮你检查出所有对它的写操作,发出警告,在编译阶段防止有意或者无意的修改。这样一来,const 常量用起来就相对安全一点。所以在设计函数的时候,将参数用 const 修饰的话,一个是可以保证效率,另一个是保证安全。

除此之外,const 还能声明在成员函数上,const 被放在了函数的后面,表示这个函数是一个“常量”,函数的执行过程是 const 的,不会修改对象的状态(即成员变量),比如:

class DemoClass final
{
private:
    const long  MAX_SIZE = 256;    // const成员变量
    int         m_value;           // 成员变量
public:
    int get_value() const        // const成员函数
    {
        // error: assignment of member ‘DemoClass::m_value’ 
        // in read-only object
        m_value = 100;
        return m_value;
    }
};

const 用于指针还有其他情况,const 放在声明的最左边,表示指向常量的指针( pointer to const int),指针指向的是一个“只读变量”,不允许修改:

   int x = 100;
   const int* px = &x; 
   *px = 102; // error: assignment of read-only

const 放在 “*” 的右边,表示指针是常量(const pointer to int),指针不能被修改,而指向的变量可以被修改:

   int x = 100;
   int b = 150;
   int* const px = &x; 
   *px = 102; // success
   px = &b; // error: assignment of read-only location

还有一种就是 const 放在 “*” 两边,表示指针和指向常量的都是指针(const pointer to const int),表示指针和变量都不能修改,具体的大家可以去试试。需要注意的是下面几种情况是相等的:

  • const int * == int const *
  • const int * const == int const * const

其实这也引出了 David Anderson 的这篇文章《 The “Clockwise/Spiral Rule”》,这篇文章主要就是讲解如何用顺时针的方式去理解一些 C 语言的复杂的声明。

比如对于下面的这个声明:

       +-------+
       | +-+   |
       | ^ |   |
  char *str[10];
   ^   ^   |   |
   |   +---+   |
   +-----------+
  • 首先想到的是 str 是什么?我们以 str 为顺时针转动,先遇到了 [ ,所以表示 str 是个数组,所以 str 是一个容量为 10 的数组;
  • 然后我们再顺时针移动,下一个遇到的是 * ,那么表示这是一个指针,那么 str 是一个容量为 10 的数组指针;
  • 我们继续顺时针转动,发现遇到了行结束符 ;,跳过它继续转动,发现遇到了 char,那么表示 str 是一个 char 类型,容量为 10 的数组指针。

所以相对的,我们用这个方法去理解 const 用于指针的情况,我借用一下 https://stackoverflow.com/questions/1143262/what-is-the-difference-between-const-int-const-int-const-and-int-const 这里面一位老哥画的图,其实就很好理解了:

pointer to int

const pointer to int const

pointer to int const

pointer to const int

const pointer to int

constexpr

constexpr 它是在 C++ 11 被引进的,它的字面意思是 constant expression,常量表达式。它可以作用在变量和函数上。一个 constexpr 变量是一个编译时完全确定的常数。一个 constexpr 函数至少对于某一组实参可以在编译期间产生一个编译期常数。

需要注意的是 const 并未区分出编译期常量和运行期常量,并且 const 只保证了运行时不直接被修改,而 constexpr 是限定在了编译期常量。所以在 constexpr 出来之后, const 的职责被拆分出来一部分,只作可读语义的保证,而常量语义交给了 constexpr 负责。

在 C++11 以后,建议凡是常量语义的场景都使用 constexpr,并且由于它是一个编译期常数,所以它甚至可以用在模板上,例如:

template<int N> class C{};

constexpr int FivePlus(int x) {
  return 5 + x;
}

void f(const int x) {
  C<x> c1;            // Error: x is not compile-time evaluable.
  C<FivePlus(6)> c2;  // OK
} 

关于 const 和 constexpr 的提问在 stackoverflowzhihu 这里讨论了很多,更详细的可以自己点进去看一下。

C++17 & if constexpr

在 C++17 的时候多了 if constexpr 这样的语法,使得模板编程的可读性更好。

我们先看个例子,在 C++11/14 的时候,我们要使用前面讲到的 enable_if 模板的话,通常要实现两个 close_enough 模板:

template <class T> constexpr T absolute(T arg) {
   return arg < 0 ? -arg : arg;
}

template <class T> 
constexpr enable_if_t<is_floating_point<T>::value, bool> 
close_enough(T a, T b) {
   return absolute(a - b) < static_cast<T>(0.000001);
}
template <class T>
constexpr enable_if_t<!is_floating_point<T>::value, bool> 
close_enough(T a, T b) {
   return a == b;
}

但是在 C++17 中配合 if constexpr 这样的语法可以简化成一个 close_enough 模板,并且将常量抽离出来变成 constexpr 变量:

template <class T> constexpr T absolute(T arg) {
   return arg < 0 ? -arg : arg;
}

template <class T>
constexpr auto precision_threshold = T(0.000001);

template <class T> constexpr bool close_enough(T a, T b) {
   if constexpr (is_floating_point_v<T>)  
      return absolute(a - b) < precision_threshold<T>;
   else
      return a == b;
}

使用 if constexpr 编译器会在编译的时候计算这个分支是否符合条件,如果不符合条件会做优化丢弃掉这个分支。

Reference

https://time.geekbang.org/column/article/238486

https://stackoverflow.com/questions/1143262/what-is-the-difference-between-const-int-const-int-const-and-int-const

https://c-faq.com/decl/spiral.anderson.html

https://stackoverflow.com/questions/14116003/whats-the-difference-between-constexpr-and-const

https://www.zhihu.com/question/35614219

扫码_搜索联合传播样式-白色版 1