5

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

 1 year ago
source link: https://www.luozhiyun.com/archives/756
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

Posted on 2023年1月28日2023年1月28日 by luozhiyun

在使用 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

CategoriescppTagscpp


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK