C++学习:C++11扩展:constexpr特性
之前我们已经学习了C++11的一些特性。那些十分重要。C++11对于C++整个的发展也是转折性的,受限于篇幅限制我们并没有完全讲完,本期我们就来学习C++11的其他特性。
在接触到更高标准的C++特性之后,我更推荐大家去这个网站查询:cppreference.com
相关代码已经上传至作者的个人gitee:楼田莉子/CPP代码学习喜欢请点个赞谢谢。
目录
顶层const/底层const
constexpr
常量表达式
constexpr函数
字面量
constexptr函数在C++14的扩展
constexptr函数在C++17的扩展
constexptr函数在C++20的扩展
动态内存分配的编译期⽀持
标准库的constexpr化
try-catch 的全⾯⽀持
constexpr 联合体(union)
constexpr 可变(mutable)成员
constexpr 虚函数⽀持
type_traits扩展
is_pointer函数
顶层const/底层const
指针本⾝是⼀个对象,它有可以指向另⼀个对象,因此指针涉及到本⾝是不是const和指向对象是不是const的问题,C++⽤为了好区分,把本⾝被const修饰叫做顶层const,把指向的对象被const修饰叫做底层const。
⼤多数对象被const修饰都叫顶层const,指针被const修饰时,*左边的const叫底层const,*右边的const叫做顶层const。
const修饰引⽤时,这个const是底层const。
int main()
{int i = 0;int* const p1 = &i; // 顶层constconst int ci = 42; // 顶层constconst int* p2 = &ci; // 底层constconst int& r = ci; // 底层constreturn 0;
}
constexpr
常量表达式
常量表达式是指值不会改变并且在编译过程中就能得到计算结果的表达式,字⾯值、常量表达式初始化的const对象都是常量表达式,要注意变量初始化的const对象不是常量表达式。
constexpr(constant expression)是C++11引⼊的⼀个关键字,⽤于指定常量表达式。它允许在编译时计算表达式的值,从⽽提⾼运⾏时性能并增强类型安全性。
constexpr可以修饰变量,constexpr修饰的变量⼀定是常量表达式,且必须⽤常量表达式初始化,否则会报错。
constexpr可以修饰指针,constexpr修饰的指针是顶层const,也就是指针本⾝。
以以下代码为例,之所以没办法确定d不是常量表达式的原因为中间的过程中可能会对c进行操作而修改其值,因此d不是常量表达式。同理调用size函数的时候因为无法确定size函数是否为一个定值(这里只是举了一个方便理解的样例,实际中很少有这样的代码)所以e也不是常量表达式
int size()
{int n = 10;return n;
}int main()
{const int a = 1; // a是常量表达式const int b = a + 1; // b是常量表达式int c = 1; // c不是常量表达式const int d = c; // d不是常量表达式const int e = size(); // e不是常量表达式return 0;
}
constexpr函数
官方文档:constexpr 说明符(自 C++11 年起)- cppreference.com
constexpr普通函数,要求函数声明的参数和返回值都是字⾯值类型(整形、浮点型、指针、引⽤等),函数返回值类型不能是空。要求函数体中,只包含⼀条 return 返回语句,不能定义局部变量,循环条件判断等控制流,并且返回值必须是常量表达式。
constexpr int size()
{return 10;
}
constexpr int func(int x)
{return 10 + x;
}
constexpr int factorial(int n)
{return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr int fxx(int x)
{int i = x;i++;//cout << i << endl;return 10 + x;
}
void test3()
{constexpr int x=size();constexpr int y=func(1);constexpr int z=factorial(5);cout << x << endl;cout << y << endl;cout << z << endl;}
void test4()
{// 编译时,N会被直接替换为10,constexpr函数默认就是inlineconstexpr int N1 = size();int arr1[N1];// func传10时,func函数返回值是常量表达式,所以N2是常量表达式constexpr int N2 = func(10);int arr2[N2];// func传10时,func函数返回值是常量表达式,所以N2是常量表达式int i = 10;constexpr int N3 = func(i); // 报错func返回的不是常量表达式int N4 = func(i); // 不报错constexpr函数返回的不⼀是常量表达式constexpr int fact5 = factorial(5); // 编译时计算//constexpr修饰的函数可以有⼀些其他语句,但是这些语句运⾏时可以不执⾏任何操作就可以// 如类型别名、空语句、using声明等constexpr int N5 = fxx(10); // 报错}
constexpr构造函数,constexpr不能修饰⾃定义类型,但是⽤constexpr修饰类的构造函数后可以就可以。该类的所有成员变量必须是字⾯类型(literal type),constexpr构造函数必须在初始化列表初始化所有成员变量,构造对象实参必须使⽤常量表达式,函数体必须为空,析构函数必须是平凡的不做任何实际清理⼯作。
constexpr成员函数,constexpr成员函数⾃动成为 const 成员函数,这意味着它们不能修改对象的成员变量,其他要求跟普通函数⼀样。另外constexpr成员函数不能是虚函数。
constexpr 可以修饰模板函数,但由于模板中类型的不确定性,因此模板函数实例化后的函数是否符合常量表达式函数的要求也是不确定的。C++11 标准规定,如果 constexpr 修饰的模板函数实例化结果不满⾜常量表达式函数的要求,则 constexpr 会被⾃动忽略,即该函数就等同于⼀个普通函数。
//Date.h
#pragma once
class Date
{
public:constexpr Date(int year, int month, int day): _year(year), _month(month), _day(day){//cout << "constexpr Date(int year, int month, int day)" << endl;}constexpr int GetYear() const{return _year;}
private:int _year;int _month;int _day;
};
//test.cpp
template<typename T>
constexpr T Func(T t)
{return t;
}
void test5()
{int x = 2025;//constexpr Date d0(x, 9, 8); // 报错constexpr Date d1(2025, 9, 8);constexpr int y = d1.GetYear();Date d2(2025, 8, 11);int z = d2.GetYear();string ret1 = Func("111111"); // 普通函数constexpr int ret2 = Func(10);
}
C++11中对constexpr函数要求较多,C++14/C++17/C++20中会逐步放开,后面会详细讲解
字面量
字面值类型是可以在编译时确定其值的类型,主要用于constexpr
上下文。
// 基本类型
int a = 42; // 整型是字面值类型
double b = 3.14; // 浮点型是字面值类型
char c = 'A'; // 字符型是字面值类型
bool d = true; // 布尔型是字面值类型// 指针和引用
int* ptr = nullptr; // 指针是字面值类型
const char* str = "Hello"; // 字符串字面值是字面值类型// 用户定义的字面值类型(满足特定条件)
struct Point {int x, y;constexpr Point(int x, int y) : x(x), y(y) {}
};constexpr Point p(1, 2); // Point是字面值类型
constexptr函数在C++14的扩展
C++14最显著的改进是⼤幅放宽了对constexpr函数的限制,使其语法和功能更接近普通函数。 函数限制的全⾯放宽
局部变量 :允许声明和初始化局部变量(只要在constexpr上下⽂中使⽤)
控制流语句 :⽀持if条件分⽀、for/while循环、switch语句等
多return语句 :函数体不再限于单⼀return语句
// C++14允许的constexpr函数⽰例
constexpr int factorial(int n)
{int res = 1; // 允许局部变量for (int i = 2; i <= n; ++i) { // 允许循环res *= i;}return res; // 单⼀ return
}
constexpr size_t stringLength(const char* str)
{size_t len = 0;while (str[len] != '\0')++len;return len;
}
void test6()
{constexpr size_t len = stringLength("Hello"); // 编译期计算:5
}
⽀持更复杂的返回类型:如void返回,⾃定义类、STL容器(std::array)、其他符合constexpr要求的复合类型
//支持更复杂的类型
struct Point {constexpr Point(double x, double y) : x(x), y(y) {}double x, y;
};
constexpr Point midpoint(Point a, Point b) {return Point((a.x + b.x) / 2, (a.y + b.y) / 2);
}
constexpr std::array<int, 5> createArray() {std::array<int, 5> arr{};for (size_t i = 0; i < arr.size(); ++i) {arr[i] = i * i;} return arr;
}
constexpr int fibonacci(int n) {return (n <= 1) ? n : (fibonacci(n - 1) + fibonacci(n - 2));
}
void test7()
{Point p1 = midpoint({ 1.1,1.1 }, { 2.2,2.2 });constexpr Point p2 = midpoint({ 1.1,1.1 }, { 2.2,2.2 });constexpr std::array<int, 5> a1 = createArray();constexpr int fibArray[] = {fibonacci(0), fibonacci(1), fibonacci(2), fibonacci(3),fibonacci(4), fibonacci(5), fibonacci(6), fibonacci(7)};
}
constexptr函数在C++17的扩展
C++17 对 constexpr 进⾏了重⼤扩展,使其能⼒⼤幅提升,进⼀步模糊了编译时和运⾏时的界限。 if constexpr - 编译期条件分⽀
if constexpr 是 C++17 引⼊的⼀种条件编译语句,它允许在编译时根据常量表达式的结果决定编译哪部分代码,未选择的分⽀代码不会编译成指令,直接丢弃。
//C++17允许的constexpr函数⽰例
template <typename T>
auto get_value(T t)
{//is_pointer_v 是 C++17 中引入的一个类型特性检查函数,// 用于在编译时检查一个类型是否为指针类型。//来源于<type_traits> 是C++标准库中非常重要的头文件,// 它提供了一系列编译时类型特性查询和转换的模板工具。if constexpr (std::is_pointer_v<T>){return *t; // 仅当T为指针类型时实例化}else{return t; // ⾮指针类型时实例化}
}
void test8()
{// 使⽤⽰例int x = 42;auto v1 = get_value(x); // 返回x本⾝auto v2 = get_value(&x); // 解引⽤返回42
}
constexpr lambda 表达式:
lambda表达式可标记为constexpr
捕获必须是编译期常量
函数体需满⾜constexpr函数要求
//lamabda表达式
//constexpr lambda⽰例
void test9()
{constexpr int n = 10;int y = 0;constexpr auto square = [n](int x) constexpr { return x * x * n; };constexpr int result = square(5); // 编译期计算:250
}
constexptr函数在C++20的扩展
C++20标准对constexpr关键字进⾏了⾰命性的增强,将编译期计算能⼒提升到了前所未有的⾼度。这些改进不仅⼤幅扩展了constexpr的应⽤范围,还使其成为现代C++元编程和性能优化的核⼼⼯具。下⾯将从多个维度全⾯解析C++20中constexpr的关键演进及其深远影响。
动态内存分配的编译期⽀持
• new / delete ⽀持:允许在 constexpr 上下⽂中使⽤动态内存分配
• 编译期容器:使得 std::vector 和 std::string 等容器的编译期实现成为可能
• 内存⽣命周期:所有分配的内存在编译期必须被释放
//C++20允许的constexpr函数⽰例
constexpr int dynamic_memory_example() {int* p = new int{ 42 }; // 编译期分配int value = *p;delete p; // 必须显式释放return value;
}
void test10()
{constexpr int v = dynamic_memory_example(); // 42
}
标准库的constexpr化
标准::查找, 标准::find_if, 标准::find_if_not - cppreference.com
std::sort - cppreference.com
std::array - cppreference.com
std::vector<T,Allocator>::vector - cppreference.com
#include<iostream>
#include<vector>
#include<array>
#include<string>
#include<algorithm>
using namespace std;
// 编译报错:失败原因是未解除分配已分配的存储
// 这⾥可以看到虽然⽀持了constexpr函数中new/delete
// 但是还要求必须编译器释放内存,所以实际库⾥⾯constexpr化还相对有限
//constexpr std::vector<int> create_vector() {
// std::vector<int> v{ 1, 2, 3 }; // 编译期构造
// v.push_back(4); // 编译期操作
//
// return v;
//}
constexpr auto sort_example() {std::array<int, 5> arr{ 5, 3, 4, 1, 2 };std::sort(arr.begin(), arr.end()); // 编译期排序return arr;
}
void test1()
{// constexpr auto vec = create_vector(); // 编译期⽣成{1,2,3}// constexpr string s1("111111111111");// constexpr vector<int> v1(10, 1);constexpr array<int, 10> a1 = { 3,2,1,4,5 };vector<int> v2 = { 3,2,1,4,5 };sort(v2.begin(), v2.end());//sort(a1.begin(), a1.end());auto it1 = find(v2.begin(), v2.end(), 3);// 相对有限⽀持的constexprconstexpr auto sorted = sort_example(); // {1,2,3,4,5}constexpr auto it2 = find(a1.begin(), a1.end(), 4);static_assert(*it2 == 4, "编译期查找");
}
try-catch 的全⾯⽀持
完整语法⽀持:允许 try-catch 块
实际限制:不能真正抛出异常(否则不是常量表达式)
错误处理:主要⽤于模板约束和编译期错误检测,异常必须在编译期捕获和处理,不能传播到运⾏时
//try _catch语句完善
constexpr int safe_divide(int a, int b) {try {if (b == 0)throw "Division by zero";elsereturn a / b;} catch(...) {return 0; // 编译期异常处理}
}
void test2()
{constexpr int val1 = safe_divide(10, 2); // 5constexpr int val2 = safe_divide(10, 0); // 报错
}
constexpr 联合体(union)
• 编译期活跃成员切换:可以在编译期改变联合体的活跃成员
• constexpr构造函数:允许定义constexpr构造函数来初始化联合体
• 成员访问限制:只能访问当前活跃成员(编译期检查)
// constexpr 联合体(union)
constexpr union Data
{int i;float f;constexpr Data(int val) : i(val) {} // 初始化整数成员constexpr Data(float val) : f(val) {} // 初始化浮点成员
};
void test3()
{constexpr Data d1(42); // 活跃成员是iconstexpr Data d2(3.14f); // 活跃成员是f//constexpr float temp = d1.f; // 错误:访问⾮活跃成员(编译失败)constexpr int temp = d1.i;
}
constexpr 可变(mutable)成员
constexpr成员函数中,成员变量是不能修改的,但是我们定义成员变量时,加上mutable修饰,这个成员变量在constexpr成员函数中就可以修改了。
//constexpr 可变(mutable)成员
class A {mutable int _i;int _j;
public:constexpr A(int i, int j):_i(i), _j(j){}constexpr int Func() const{++_i; // 可以修改// ++_j; // 不能修改return _i + _j;}
};
void test4()
{constexpr A aa(1, 1);constexpr int ret = aa.Func();
}
constexpr 虚函数⽀持
之前虚函数是不⽀持定义为constexpr函数的,C++20中开始⽀持
//constexpr 虚函数⽀持
class Base {
public:virtual constexpr int value() const { return 1; }
};
class Derived : public Base {
public:constexpr int value() const override { return 2; }
};
constexpr int get_value(const Base& b) {return b.value(); // 编译期多态调⽤
}
void test5()
{constexpr int ret1 = get_value(Base());constexpr int ret2 = get_value(Derived());
}
type_traits扩展
前面我们接触过is_pointer函数,它来自于<type_traits>头文件
is_pointer函数
#include <iostream>
#include <type_traits>
using namespace std;
//is_pointer_v 是 C++17 中引入的一个类型特性检查函数,用于在编译时检查一个类型是否为指针类型。
//
//基本作用
//is_pointer_v<T> 在编译时返回一个 bool 值:
//
//如果 T 是指针类型,返回 true
//
//如果 T 不是指针类型,返回 false
//注意事项
//不检查空指针:is_pointer_v 只检查类型,不检查指针是否为空或有效
//
//不包括智能指针:std::shared_ptr<T>, std::unique_ptr<T> 等智能指针返回 false
//
//包括各种指针:包括 T*, const T*, volatile T*, T* const 等变体
// C++14 及之前
template<typename T>
void old_style(T value)
{if (std::is_pointer<T>::value) {// 这个分支在运行时判断,可能产生性能开销std::cout << *value << std::endl;}
}// C++17 及之后
template<typename T>
void modern_style(T value)
{if constexpr (std::is_pointer_v<T>) {// 这个分支在编译时决定,零运行时开销std::cout << *value << std::endl;}
}
//基本用法
void test1()
{std::cout << std::boolalpha;std::cout << "is_pointer_v<int> = " << std::is_pointer_v<int> << std::endl; // falsestd::cout << "is_pointer_v<int*> = " << std::is_pointer_v<int*> << std::endl; // truestd::cout << "is_pointer_v<int**> = " << std::is_pointer_v<int**> << std::endl; // truestd::cout << "is_pointer_v<char*> = " << std::is_pointer_v<char*> << std::endl; // truestd::cout << "is_pointer_v<void*> = " << std::is_pointer_v<void*> << std::endl; // truestd::cout << "is_pointer_v<const int*> = " << std::is_pointer_v<const int*> << std::endl; // true// 对于非指针类型std::cout << "is_pointer_v<int&> = " << std::is_pointer_v<int&> << std::endl; // falsestd::cout << "is_pointer_v<int[]> = " << std::is_pointer_v<int[]> << std::endl; // falsestd::cout << "is_pointer_v<std::string> = " << std::is_pointer_v<std::string> << std::endl; // false
}
//模板编程
// 方案1:使用 if constexpr (C++17)
template<typename T>
void process(T value) {if constexpr (std::is_pointer_v<T>) {// 如果是指针,解引用std::cout << "Pointer value: " << *value << std::endl;}else {// 如果不是指针,直接使用std::cout << "Non-pointer value: " << value << std::endl;}
}// 方案2:使用 SFINAE
template<typename T>
std::enable_if_t<std::is_pointer_v<T>>
print_value(T ptr) {std::cout << "Pointer: " << *ptr << std::endl;
}template<typename T>
std::enable_if_t<!std::is_pointer_v<T>>
print_value(T value) {std::cout << "Value: " << value << std::endl;
}
void test2()
{int x = 42;int* ptr = &x;process(x); // 输出: Non-pointer value: 42process(ptr); // 输出: Pointer value: 42print_value(x); // 输出: Value: 42print_value(ptr); // 输出: Pointer: 42
}
//编译时断言
template<typename T>
class SmartPointer {T* ptr;public:// 确保模板参数是指针类型static_assert(std::is_pointer_v<T>,"SmartPointer can only be instantiated with pointer types");SmartPointer(T* p) : ptr(p) {}// ... 其他成员函数
};
void test3()
{// 使用int value = 10;SmartPointer<int*> sp(&value); // 正确 - int 不是指针类型SmartPointer<int> sp2(&value); // 编译错误 - int* 是指针类型
}
//安全内存管理
template<typename T>
void safe_delete(T ptr) {static_assert(std::is_pointer_v<T>,"safe_delete requires a pointer type");if (ptr != nullptr) {delete ptr;}
}
void test4()
{// 使用int* dynamic_int = new int(100);safe_delete(dynamic_int); // 正确// int not_a_pointer = 5;// safe_delete(not_a_pointer); // 编译错误
}
int main()
{test1();test2();test3();test4();return 0;
}
头文件<type_traits>
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <type_traits>
using namespace std;
//<type_traits> 是C++标准库中非常重要的头文件,它提供了一系列编译时类型特性查询和转换的模板工具。
//<type_traits> 是C++标准库中非常重要的头文件,它提供了一系列编译时类型特性查询和转换的模板工具。
//
//主要功能分类
//1. 类型特性查询(Type Traits)
//基本类型类别检查
void test1()
{std::is_void<void>::value; // truestd::is_integral<int>::value; // truestd::is_floating_point<float>::value;// truestd::is_array<int[]>::value; // truestd::is_pointer<int*>::value; // truestd::is_reference<int&>::value; // true
}
//类型修饰符检查
void test2()
{std::is_const<const int>::value; // truestd::is_volatile<volatile int>::value; // truestd::is_signed<int>::value; // truestd::is_unsigned<unsigned>::value; // true
}
//复合类型检查
class MyClass {};
union MyUnion {};
enum MyEnum {};
void test3()
{class MyClass {};std::is_class<MyClass>::value; // truestd::is_union<MyUnion>::value; // truestd::is_enum<MyEnum>::value; // true
}
//类型关系检查
class Base {};
class Derived : public Base {};
void test4()
{std::is_same<int, int>::value; // truestd::is_same<int, double>::value; // falsestd::is_base_of<Base, Derived>::value; // truestd::is_convertible<int, double>::value; // true
}
//类型转换(Type Transformations)
//添加 / 移除修饰符
void test5()
{std::remove_const<const int>::type; // intstd::add_const<int>::type; // const intstd::remove_pointer<int*>::type; // intstd::add_pointer<int>::type; // int*std::remove_reference<int&>::type; // intstd::add_lvalue_reference<int>::type; // int&
}
//符号操作
void test6()
{std::make_signed<unsigned int>::type; // intstd::make_unsigned<int>::type; // unsigned int
}
//类型属性查询
struct MyStruct {int a;double b;
};
void test7()
{std::cout << std::alignment_of<int>::value; // 对齐要求std::cout << std::rank<int[3][4]>::value; // 数组维度数:2std::cout << std::extent<int[3][4], 0>::value; // 第0维大小:3std::cout << std::extent<int[3][4], 1>::value; // 第1维大小:4
}
//其他重要特性
//可调用性检查
void func(int);
void test8()
{std::is_function<decltype(func)>::value; // trueauto lambda = [](int x) { return x * 2; };std::is_invocable<decltype(lambda), int>::value; // true
}
//成员检查
struct HasMember {int value;void method() {}
};
void test9()
{std::is_member_object_pointer<decltype(&HasMember::value)>::value; // truestd::is_member_function_pointer<decltype(&HasMember::method)>::value; // true
}//模板元编程
template<typename T>
void process(T value) {if constexpr (std::is_integral_v<T>) {// 整数类型的处理std::cout << "Integral: " << value << std::endl;}else if constexpr (std::is_floating_point_v<T>) {// 浮点类型的处理std::cout << "Floating: " << value << std::endl;}else {// 其他类型std::cout << "Other type" << std::endl;}
}
//2. SFINAE(替换失败不是错误)
// 只有T是整数类型时才启用这个函数
template<typename T>
std::enable_if_t<std::is_integral_v<T>, T>
increment(T value) {return value + 1;
}// 只有T是浮点类型时才启用这个函数
template<typename T>
std::enable_if_t<std::is_floating_point_v<T>, T>
increment(T value) {return value + 1.0;
}
//3. 完美转发和类型推导
template<typename T>
void forward_example(T && arg) {using DecayedType = std::decay_t<T>; // 移除引用和cv限定符// 使用DecayedType进行安全存储
}
//4. 编译时断言template<typename Iterator>
void algorithm(Iterator first, Iterator last) {static_assert(std::is_same_v<typename std::iterator_traits<Iterator>::iterator_category,std::random_access_iterator_tag>,"This algorithm requires random access iterators");// 算法实现...
}
////C++17 / 20 的便利改进
////变量模板(_v后缀)
//
//// C++14之前
//std::is_integral<int>::value;
//
//// C++17之后
//std::is_integral_v<int>; // 更简洁
////类型别名模板(_t后缀)
//
//// C++14之前
//typename std::remove_const<T>::type;
//
//// C++14之后
//std::remove_const_t<T>; // 更简洁int main()
{test1();test2();test3();test4();test5();test6();test7();test8();test9();return 0;
}
本期关于这个特性我们先讲到这里,后续我们还会学习更多关于C++的现代特性
封面图自取: