[C++关键字] const/constexpr

const/constexpr1

  • 尽可能的使用constexpr
  • 对于不变的变量,尽量用const修饰

const 与 宏

const 与 类

  • 1. 修饰类变量-对象:则只能调用const 函数, 不能调用非const函数; 除非在成员变量前面添加mutable 显性指明可变。
  • 2. 修饰类中成员变量:必须在初始化列表中进行初始化
  • 3. 静态常量和普通常量类似: 推荐放到类外,在相应的.cc/.cpp源文件中进行初始化
  • 4. 修饰成员函数:表示函数中成员变量不可被改变,可以被const 对象调用,也可以被普通对象调用;但是这种函数中不能调用非const类型的函数。
// test.h
class Test {
 public:
  Test(int var): const_var_(var) {}  // 2. 修饰类中成员变量
  const int GetVarible() const { return const_var_;} // 4. 修饰成员函数
  void SetVariable(int var) { mutable_var_ = var; }
  mutable int mutable_var_;
  static const int static_const_var_; // 3. 静态常量和普通常量类似
 private:
  const int const_var_; // 2. 修饰类中成员变量
};

// test.cc
static const int Test::static_const_var_ = 0;

const Test test(0); // 1. 修饰类变量-对象 
test.GetValue(); // 1. 修饰类变量-对象
test.mutable_var_ = 200;

const 与 指针

  • 1. 指针常量:指向的内容是常量,内容不可变,但是指针可变
  • 2. 常量指针:指针为常量,指针不可变,但是内容可变
  • 3. 常量指针常量:指针和内容都是常量,都不可变
  • 区分哪个是常量的记忆秘诀:const 离哪个量近就是修饰哪个, 如下所示,var_1,var_2 就是const与char类型近,所以常量是对char类型的,也就是内容来约定的; 而var_3是 const与 var_3近,又因为var_3是 指针变量,所以const修饰的是指针。
const char * var_1; // 1. 指针常量
char const * var_2; // 1. 指针常量
char * const var_3; // 2. 常量指针
const char * const var_4; // 3. 常量指针常量

const 其他

  • const 与 引用: 内容不可更改,且没有copy开销。 一般参数传递时推荐的写法。
  • const 转换: 非const转const 很方便;但是const 转非const也就是remove const是麻烦的,一般不支持隐式转换。可以考虑用remove_const,推荐使用const_cast来实现。
// 注意用模板的机制,实现remove const操作
template<typename T>
struct remove_const
{
	typedef T Type;
};

template<typename T>
struct remove_const<const T> // 点睛之笔
{
	typedef T Type;
};

template<typename T>
using RemoveConstT = typename remove_const<T>::Type;
  • 顶层const: 修饰的对象本身不能改变,如const变量,指针常量等
  • 底层const:修饰的指针或者引用的对象不能改变, 本身可以改变,但是所指内容不可以改变,如 常量指针。 这种const只能用const_cast<>方式移除,但是有风险。

constexpr (C++11之后)

在编译期间就能完成执行的代码就最好改为constexpr描述, 这样能提升运气期间的性能,因为编译期间已经确定了表达式的值,不用在运行期间再执行表达式所以尽可能的使用constexpr

  • 修饰表达式:如果一个常量表达式能在编译阶段确定,那么就需要设置为constexpr, 另外如果我们想让一个表达式在编译期间就能得到值,那么我们也需要设置成constexpr。把它放到变量定义前,那么用来初始化这个变量的表达式「必须」是常量表达式。
  • 修饰对象: 对象是const的,对象需要编译期间确定。
  • 修饰函数:如果不能在编译时期计算出来,那么constexpr修饰的函数就和普通函数一样了, 如果能就是一个const 常量存于代码中,用于运行时使用。
  • 检测constexpr函数是否产生编译时期值的方法很简单:就是利用std::array需要编译期常值才能编译通过的小技巧。这样的话,即可检测你所写的函数是否真的产生编译期常值了。
  • constexpr与virtual功能上正好相反
constexpr int GetNumber() {...}

const int num = GetNumber(); // 如果GetNumber是个模板递归,那么需要在编译期间确定值,那么就需要将const改为constexpr
constexpr int num = GetNumber();  // 则编译期间能确定num的值
  • 在实际规范地使用用时,一般把能在编译期确定的表达式或函数定义为constexpr,且变量名kVariable方式定义表示一种常量。而运行期间的定义为const。
  • constexpr和const 一起用的情况:
constexpr char* kName = "myname"; // ISO C++11 does not allow conversion from string literal to 'char *'
// the following code is ok.
constexpr char const* kName = "myname";
  • constexpr 能修饰自己定义的类型,需要定义 class的时候,用constexpr修饰一些成员函数, 下面例子是waymo-open-dataset的源码, 为什么这样写:
    • constexpr is used here to allow the Vec3d class to be used in constant expressions.

// A simple 3 vector class with arithmetic, scaling and normalization.
class Vec3d {
 public:
  constexpr Vec3d() : x_(0.0), y_(0.0), z_(0.0) {}
  constexpr Vec3d(double x, double y, double z) : x_(x), y_(y), z_(z) {}

  constexpr double x() const { return x_; }
  constexpr double y() const { return y_; }
  constexpr double z() const { return z_; }

  constexpr double Sqr() const { return x_ * x_ + y_ * y_ + z_ * z_; }
  double Length() const { return std::sqrt(Sqr()); }

  constexpr Vec3d operator+(const Vec3d& p) const {
    return Vec3d(x_ + p.x_, y_ + p.y_, z_ + p.z_);
  }

  constexpr Vec3d operator-() const { return Vec3d(-x_, -y_, -z_); }

  constexpr Vec3d operator-(const Vec3d& p) const {
    return Vec3d(x_ - p.x_, y_ - p.y_, z_ - p.z_);
  }

  constexpr Vec3d operator*(double s) const {
    return Vec3d(x_ * s, y_ * s, z_ * s);
  }

  constexpr Vec3d operator/(double s) const {
    return Vec3d(x_ / s, y_ / s, z_ / s);
  }

  Vec3d Normalized() const {
    const double len = Length();
    if (len > 0) {
      return Vec3d(x_ / len, y_ / len, z_ / len);
    }
    return Vec3d(0, 0, 0);
  }

  constexpr bool operator==(const Vec3d& pp) const {
    return x_ == pp.x() && y_ == pp.y() && z_ == pp.z();
  }

 private:
  double x_, y_, z_;
};

constexpr Vec3d operator*(double scale, const Vec3d& pp) { return pp * scale; }

// 使用以下写法是可以的,如果没有上面的定义修饰,下面的几个写法就有问题,也就是不能将下面的变量在编译阶段就确定。
constexpr Vec3d v1{1, 2, 3};     // Constructed in a constant expression
constexpr double x = v1.x();     // Call getter in a constant expression
constexpr Vec3d v2 = v1 * 2;     // Multiplication in a constant expression 

  • static constexpr在函数中声明固定的已知变量时使用
    • 具体原因:If the value can be determined during compilation we should use constexpr. It is always faster. You could also consider adding static if you think the function will be used very often. 因此,在大多数情况下,非静态 const(expr) 数组必须在每次调用时在堆栈上重新创建,这破坏了能够在编译时计算它的点。
void function() {
 static constexpr int kSize=2;
}

reference


  1. 参考知乎博文 ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值