constexpr关键字详解

概念

constexpt关键字是在c++11版本引入的重要特性,它的主要作用是在编译时运算,提供编译时常量。

基本用法

常量表达式

// 1. 基本常量
constexpr int value = 42;                      // 编译时常量
constexpr double pi = 3.14159;                 // 编译时常量
constexpr int square(int x) { return x * x; }  // 编译时函数

// 2. 在编译时计算
constexpr int result = square(5);              // 编译时计算结果为25

函数声明

注意在c++11版本,仅能包含一条return语句

class Math {
public:
    // constexpr函数
    constexpr int add(int a, int b) const {
        return a + b;
    }
    
    // constexpr构造函数
    constexpr Math() : value(0) {}
    
private:
    int value;
};

constexpt与const的区别

// const:运行时常量
const int var1 = std::rand();      // 运行时确定值

// constexpr:编译时常量
constexpr int var2 = 42;           // 必须在编译时确定值

void example() {
    const int arr_size1 = rand();  // OK:运行时确定
    int arr1[arr_size1];          // 错误:数组大小必须是常量表达式
    
    constexpr int arr_size2 = 10; // 编译时确定
    int arr2[arr_size2];          // OK:可以用作数组大小
}

 不同版本constexpr特性

c++11版本

// 1. 函数体只能包含一条 return 语句
constexpr int square(int x) {
    return x * x;  // 只能有这一条语句
}

// 2. constexpr 构造函数的限制
class Point {
    int x, y;
public:
    constexpr Point(int ix, int iy) : x(ix), y(iy) {}  // 只能包含初始化列表
    constexpr int getX() const { return x; }
    constexpr int getY() const { return y; }
};

// 3. 递归是允许的
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

c++14版本

允许多条语句

constexpr int factorial(int n) {
    int result = 1;
    for (int i = 1; i <= n; ++i) {  // 允许循环
        result *= i;
    }
    return result;
}

允许声明变量

constexpr Point movePoint(Point p, int dx, int dy) {
    int new_x = p.getX() + dx;  // 允许局部变量
    int new_y = p.getY() + dy;
    return Point(new_x, new_y);
}

允许if语句

constexpr int abs(int x) {
    if (x < 0) {  // 允许if语句
        return -x;
    }
    return x;
}

 c++17版本

constexpr if

template <typename T>
constexpr auto process(T value) {
    if constexpr (std::is_integral_v<T>) {
        return value * 2;
    } else if constexpr (std::is_floating_point_v<T>) {
        return value + 0.5;
    } else {
        return value;
    }
}

 constexpr lambda

// constexpr lambda表达式
auto square = [](int x) constexpr { return x * x; };
constexpr int result = square(4);  // 编译期计算

// 泛型lambda
auto generic_square = [](auto x) constexpr { return x * x; };
constexpr int i_result = generic_square(4);    // int
constexpr double d_result = generic_square(4.0);  // double

c++20版本

 constexpr 虚函数

class Base {
public:
    constexpr virtual int getValue() const { return 42; }
};

class Derived : public Base {
public:
    constexpr int getValue() const override { return 84; }
};

constexpr int test() {
    Derived d;
    const Base& b = d;
    return b.getValue();  // 编译期多态
}

constexpr std::string和std::vector

constexpr std::string str = "Hello";
constexpr bool starts_with_h = str.starts_with('H');

constexpr std::vector<int> vec = {1, 2, 3};
constexpr int first = vec[0];

c++23版本

 constexpr typeinfo

constexpr auto type_name = typeid(int).name();

更多的标准库支持

std::optional

std::variant

std::function

更多算法和容器操作

 constexpr原理分析

通过上面的概念了解了constexpr是显式的指明函数或者变量在编译期运算,那么他的原理该怎么理解呢?比如对一个std::string而言使用constexpr与否到底有什么区别呢?请看下面的讲解。

std::string 通常的内存布局包括:

class string {
    struct {
        char* data;      // 指向实际字符数据的指针
        size_t size;     // 字符串长度
        size_t capacity; // 分配的内存容量
    };
    // 可能还有小字符串优化(SSO)的缓冲区
};

 使用constexpr与否的区别:

void example() {
    // 运行时版本
    std::string s1 = "Hello";
    // 执行过程:
    // 1. 在栈上分配 std::string 对象本身的空间
    // 2. 在堆上分配存储字符的空间
    // 3. 将 "Hello" 从只读数据段复制到堆内存
    // 4. 函数结束时析构 string,释放堆内存

    // 编译期版本
    constexpr std::string s2 = "Hello";
    // 编译器优化后:
    // 1. 在栈上分配 std::string 对象的空间
    // 2. string 的数据直接指向只读数据段的 "Hello"
    // 3. 不需要堆内存分配和复制
}

编译器对 constexpr std::string 可以做特殊优化:

// 编译器可能将这段代码
constexpr std::string s2 = "Hello";

// 优化成类似这样的实现
struct {
    const char* data = "Hello";  // 直接指向只读数据段
    size_t size = 5;            // 编译期确定的大小
    size_t capacity = 5;        // 编译期确定的容量
} s2;

 注意事项和限制

基本注意事项

class Restrictions {
public:
    // 1. constexpr函数必须有返回值
    constexpr void invalid() {}  // 错误:void返回类型
    
    // 2. 不能包含静态或线程局部数据
    constexpr int wrong() {
        static int x = 0;  // 错误
        return x;
    }
    
    // 3. 不能有副作用,比如调用只有在运行阶段才能进行的操作
    constexpr int no_side_effects() {
        std::cout << "Hello";  // 错误:不允许I/O操作
        return 42;
    }
};

注意事项二

  • constexpr关键字修饰的函数可以在编译阶段调用,也可以在运行时,如果强调在编译时调用一定采用constexpr修饰,否则都是在默认阶段调用
class MyClass {
    int value;
public:
    constexpr MyClass(int v) : value(v) {}
    constexpr int getValue() const { return value; }
    constexpr MyClass add(const MyClass& other) const {
        return MyClass(value + other.getValue());
    }
};

void mixed_example() {
    // 1. 编译期计算
    constexpr MyClass obj1(10);
    constexpr MyClass obj2(20);
    constexpr int sum = obj1.add(obj2).getValue();  // 编译期计算得30

    // 2. 运行时计算
    MyClass runtime_obj1(15);
    MyClass runtime_obj2(25);
    int runtime_sum = runtime_obj1.add(runtime_obj2).getValue();  // 运行时计算

    // 3. 混合使用
    constexpr MyClass const_obj(100);
    MyClass runtime_obj(50);
    
    // 以下都是运行时计算
    int val1 = const_obj.getValue();     // 虽然对象是constexpr,但这里是运行时调用
    int val2 = runtime_obj.getValue();   // 运行时调用
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值