static_assert 编译期断言
定义与作用
static_assert 是 C++11 引入的编译期断言,在编译阶段检测常量表达式条件,条件为 false 时产生编译错误并输出指定消息。
static_assert(sizeof(int) >= 4, "int 类型必须至少 4 字节");
| 对比 | static_assert | assert() |
|---|---|---|
| 检测时机 | 编译期 | 运行时 |
| 表达式要求 | 必须是常量表达式 | 任意表达式 |
| 失败后果 | 编译失败 | 运行时 abort(仅 Debug 模式) |
| 性能开销 | 零 | Debug 下有开销 |
| 错误消息 | 编译期字符串 | 运行时输出 |
| NDEBUG 影响 | 不受影响 | 被禁掉 |
核心原理
工作流程
与 enable_if / SFINAE 的关系
static_assert 的检测发生在模板实例化完成之后,不参与 SFINAE。这意味着:
template<typename T>
void process(T value) {
static_assert(sizeof(T) <= 8, "T 太大了"); // 硬错误,不参与 SFINAE
}
而 enable_if 在模板参数推导阶段就将不符合条件的候选移除:
template<typename T>
typename std::enable_if<sizeof(T) <= 8>::type
process(T value) { /* ... */ } // SFINAE 友好
C++11 vs C++17
| C++ 版本 | 语法 |
|---|---|
| C++11 | static_assert(expr, "message"),消息参数必填 |
| C++17 | static_assert(expr),消息参数可选 |
完整示例
示例一:模板参数约束
场景说明:白歌为飞翔科技设计一个固定大小的缓冲区模板,要求在编译期确保类型大小合理。
#include <iostream>
#include <cstdint>
#include <type_traits>
// 固定大小缓冲区 —— 只接受大小 ≤ 64 字节的类型
template <typename T, std::size_t Capacity>
class FixedBuffer {
// 编译期断言1:单个元素不能太大
static_assert(sizeof(T) <= 64,
"FixedBuffer: 单个元素超过 64 字节,请考虑堆分配");
// 编译期断言2:Capacity 至少为 1
static_assert(Capacity >= 1,
"FixedBuffer: 容量至少为 1");
// 编译期断言3:必须是平凡可复制类型
static_assert(std::is_trivially_copyable<T>::value,
"FixedBuffer: 元素类型必须是平凡可复制的");
public:
bool push(const T& item) {
if (size_ >= Capacity) return false;
buffer_[size_++] = item;
return true;
}
const T& operator[](std::size_t i) const { return buffer_[i]; }
std::size_t size() const { return size_; }
private:
T buffer_[Capacity]{};
std::size_t size_ = 0;
};
// 大结构体 —— 触发断言
struct LargeRecord {
char data[128]; // 超过 64 字节
};
// 非平凡类型 —— 触发断言
struct NonTrivial {
NonTrivial() { std::cout << "构造" << std::endl; } // 有用户定义构造函数
};
int main() {
std::cout << "===== 飞翔科技编译期类型检查 =====" << std::endl;
// 合法使用
FixedBuffer<int, 16> intBuf;
intBuf.push(42);
intBuf.push(100);
std::cout << "\n整数缓冲区: 已存储 " << intBuf.size() << " 个元素" << std::endl;
FixedBuffer<double, 8> doubleBuf;
doubleBuf.push(3.14159);
std::cout << "浮点缓冲区: 已存储 " << doubleBuf.size() << " 个元素" << std::endl;
// 编译期类型大小检查
std::cout << "\n[编译期断言验证]" << std::endl;
std::cout << "sizeof(int) = " << sizeof(int) << " 字节" << std::endl;
std::cout << "sizeof(long) = " << sizeof(long) << " 字节" << std::endl;
static_assert(sizeof(int) >= 4, "int 至少 4 字节");
static_assert(sizeof(long long) >= 8, "long long 至少 8 字节");
std::cout << "所有断言通过!" << std::endl;
// 以下代码取消注释会导致编译失败:
// FixedBuffer<LargeRecord, 8> badBuf; // 元素超过 64 字节
// FixedBuffer<NonTrivial, 4> badBuf2; // 不是平凡可复制类型
// FixedBuffer<int, 0> badBuf3; // Capacity = 0
return 0;
}
预期输出:
===== 飞翔科技编译期类型检查 =====
整数缓冲区: 已存储 2 个元素
浮点缓冲区: 已存储 1 个元素
[编译期断言验证]
sizeof(int) = 4 字节
sizeof(long) = 4 字节
所有断言通过!
逐段分析:
FixedBuffer类的三个static_assert都在类定义中,在模板实例化时求值- 当用户尝试
FixedBuffer<LargeRecord, 8>时,编译器会输出 "单个元素超过 64 字节" 的错误消息并中止编译 - 这些检查在编译期完成,运行时零开销——相比运行时
if+throw方案 std::is_trivially_copyable配合static_assert是编译期类型 trait 检查的经典用法
示例二:平台兼容性与版本检查
场景说明:小崔维护飞翔科技的跨平台代码,需要确保特定假设在编译期得到验证。
#include <iostream>
#include <cstdint>
#include <cstddef>
// 飞翔科技内部工具:确保平台假设成立
namespace FeixiangPlatform {
// 断言1:指针大小匹配
static_assert(sizeof(void*) == sizeof(std::uintptr_t),
"uintptr_t 必须能容纳指针");
// 断言2:大小端检测(编译期)
constexpr bool is_little_endian() {
constexpr std::uint16_t val = 0x0102;
constexpr const unsigned char* p =
static_cast<const unsigned char*>(
static_cast<const void*>(&val));
return p[0] == 0x02;
}
static_assert(is_little_endian(),
"飞翔科技当前仅支持小端序平台");
// 断言3:特定类型的对齐要求
static_assert(alignof(double) >= alignof(int),
"double 对齐必须 ≥ int 对齐");
// 断言4:结构体填充检查
struct VersionHeader {
std::uint16_t major;
std::uint16_t minor;
std::uint32_t build;
};
static_assert(sizeof(VersionHeader) == 8,
"VersionHeader 应为 8 字节(无填充)");
} // namespace FeixiangPlatform
// 模拟不同 C++ 版本的条件编译
#if __cplusplus >= 201703L
static_assert(true, "使用 C++17 或更新标准");
#else
static_assert(__cplusplus == 201103L,
"仅支持 C++11,请升级编译器");
#endif
int main() {
std::cout << "===== 飞翔科技平台验证 =====" << std::endl;
std::cout << "\n[基础类型大小]" << std::endl;
std::cout << "sizeof(void*) = " << sizeof(void*) << std::endl;
std::cout << "sizeof(std::uintptr_t) = "
<< sizeof(std::uintptr_t) << std::endl;
std::cout << "\n[对齐信息]" << std::endl;
std::cout << "alignof(int) = " << alignof(int) << std::endl;
std::cout << "alignof(double) = " << alignof(double) << std::endl;
std::cout << "\n[VersionHeader]" << std::endl;
std::cout << "sizeof(VersionHeader) = "
<< sizeof(FeixiangPlatform::VersionHeader) << std::endl;
// 运行时辅助检测
std::uint16_t testVal = 0x0102;
const auto* bytes = reinterpret_cast<const unsigned char*>(&testVal);
std::cout << "\n[字节序检测]" << std::endl;
std::cout << "0x0102 在内存中: "
<< std::hex
<< static_cast<int>(bytes[0]) << " "
<< static_cast<int>(bytes[1])
<< std::dec << std::endl;
std::cout << (bytes[0] == 0x02 ? "小端序 (Little Endian)" : "大端序 (Big Endian)")
<< std::endl;
std::cout << "\n所有平台检查通过!" << std::endl;
return 0;
}
预期输出(Windows x64):
===== 飞翔科技平台验证 =====
[基础类型大小]
sizeof(void*) = 8
sizeof(std::uintptr_t) = 8
[对齐信息]
alignof(int) = 4
alignof(double) = 8
[VersionHeader]
sizeof(VersionHeader) = 8
[字节序检测]
0x0102 在内存中: 2 1
小端序 (Little Endian)
所有平台检查通过!
逐段分析:
sizeof(void*) == sizeof(std::uintptr_t)等价性检查确保指针能安全转换为整数——不满足时编译报错- 编译期大小端检测使用
constexpr函数配合static_assert,整个检测在编译阶段完成 alignof(double) >= alignof(int)验证对齐层级关系VersionHeader大小断言确保结构体无意外填充——这是跨平台二进制兼容的常见需求__cplusplus预定义宏检测 C++ 标准版本
易错场景与面试考点
易错场景
| 场景 | 错误表现 | 正确做法 |
|---|---|---|
static_assert 在模板中做 SFINAE | 期望失败时静默移除,实际获得硬错误 | 模板约束用 enable_if 或 C++20 requires |
| C++11 中省略消息字符串 | static_assert(expr) 编译错误 | C++11 必须写两个参数;C++17 支持单参数 |
| 条件表达式不是常量 | "expression is not constant" 错误 | 确保表达式可在编译期求值 |
| 用于检查运行时值 | 编译失败 | static_assert 只能检查编译期常量 |
| 断言放在类外非模板上下文中无条件为 false | 始终编译失败 | 模板中的作用域取决于实例化 |
常见面试问题
static_assert和assert()的本质区别?——static_assert在编译期检测常量表达式,失败导致编译错误;assert()在运行时检测任意表达式,失败在 Debug 模式下 abort。前者零开销,后者有运行时成本。static_assert能替代enable_if做模板约束吗?——不能。static_assert失败是硬错误,会导致编译失败;enable_if失败是软错误(SFINAE),只从候选集中移除。模板约束应使用enable_if或 C++20requires。static_assert(false, "...")在模板中有什么问题?——如果放在未依赖模板参数的位置,即使模板未被实例化也可能触发编译错误。需要让条件依赖于模板参数(如sizeof(T) == 0而非false)。什么时候用
static_assert而不是enable_if?——需要向用户输出明确的错误消息时用static_assert(如 "int 大小不符合预期"),需要做候选集消歧时用enable_if。两者可以配合使用:enable_if做筛选,static_assert做最终校验。
小结
static_assert(bool_constexpr, "message")在编译期检查常量条件,零运行时开销- 与运行时
assert()的本质区别:前者编译失败,后者运行时 abort(仅 Debug) - 典型场景:模板参数约束、类型大小/对齐检查、平台假设验证
static_assert不参与 SFINAE,失败 = 硬错误;模板约束应使用enable_if或 concepts- C++11 中消息参数必填,C++17 起可选