9. ⾮类型模板参数
⾮类型模板参数是⼀个具有特定类型的常量值,在 C++17 之前,⾮类型模板参数的类型必须被明确 指定。C++17 允许我们使⽤ auto 关键字作为⾮类型模板参数的占位符,编译器会根据实例化时
提供的实参⾃动推导出该参数的类型。
在 C++20 之前,⾮类型模板参数的类型被严格限制在:整型/枚举类型/指针类型/左值引
⽤/ std::nullptr_t ,C++20 打破了这⼀限制,允许浮点数,允许你使⽤满⾜特定条件的类类
型(称为"字⾯量类类型")的对象作为编译期常量,并传递给模板。字⾯量类类型C++20会具体
讲解,简单来说,它必须是⼀个可以在编译期完全构造和析构的简单、透明的数据结构,如
std::array ,简单的⾃定义结构体 struct Point { int x; int y; }; 等
#include <iostream>
#include <string>
#include <string_view>
#include <array>
#include <typeinfo>
template<auto Value>
void printValue() {
//std::cout << "Value: " << Value << std::endl;
std::cout << "Type: " << typeid(Value).name() << std::endl; // 打印类型名(编
译器相关)
std::cout << "---" << std::endl;
}
// ⼀个简单的字⾯量类类型
struct Point {
int x;
int y;
// 编译器会为我们⽣成⼀个隐式的 constexpr 构造函数
// 析构函数也是隐式 constexpr 的
};
template<auto... Values>
struct ValueList
{
ValueList()
{
// 逗号折叠表达式
((std::cout << Values<<' '), ...);
}
}; // ⼀个编译期的值列表
const char arr[] = "hello";
int main() {
printValue<42>(); // auto 被推导为 int
printValue<3.14>(); // auto 被推导为 double C++20开始才⽀持
printValue<'A'>(); // auto 被推导为 char
printValue<true>(); // auto 被推导为 bool
printValue<nullptr>(); // auto 被推导为 nullptr_t
// printValue<"hello">(); // 错误:字符串字⾯量不能作为⾮类型模板参数
printValue<arr>(); // auto 被推导为 const char*
// C++20开始⽀持字⾯量类类型作为⾮类型模板参数
printValue<Point{ 10, 20 }> ();
constexpr std::array arr{ 1, 2, 3, 4, 5 };
printValue<arr> ();
// 计算值列表中的值的类型
ValueList<1, 2.2, 'a'> vl;
//printValue<string("Hello")>(); // 报错
}
10. 嵌套命名空间定义
C ++17 引⼊了简化的嵌套命名空间定义语法,使得定义多层嵌套的命名空间更加简洁⽅便。这是对 传统命名空间定义⽅式的⼀种语法糖改进。
#include <iostream>
// C++17之前的嵌套命名空间
namespace A {
namespace B {
namespace C {
void foo() {
std::cout << "Old nested namespace style" << std::endl;
}
}
}
}
// C++17的简洁嵌套命名空间
namespace A::B::C {
void bar() {
std::cout << "New nested namespace style" << std::endl;
}
}
namespace A::B {
void qux() {
std::cout << "Partially nested namespace" << std::endl;
}
}
int main() {
A::B::C::foo();
A::B::C::bar();
A::B::qux();
return 0;
}
11. __has_include
• __has_include 是 C++17 引⼊的⼀个特殊预处理器功能,⽤于在编译时检查某个头⽂件是否可以被 包含。
__has_include (< 头⽂件名 >) 或 __has_include (" 头⽂件名 ") 它返回⼀个可以被预
处理器解释的整型常量表达式,1 表⽰头⽂件存在且可被包含 0 表⽰头⽂件不可⽤。
• 它的主要⽬的是为了编写跨平台或跨编译器的可移植代码。不同的系统或编译器版本可能提供不同 的头⽂件库。使⽤ __has_include ,你可以有条件地包含头⽂件或采取备选⽅案,从⽽避免编
译错误。
#include <iostream>
// ⽰例1: 检查标准库头⽂件
#if __has_include(<optional>)
#include <optional>
#define HAS_OPTIONAL 1
#else
#define HAS_OPTIONAL 0
#endif
// ⽰例2: 检查⾃定义头⽂件
#if __has_include("my_header.h")
#include "my_header.h"
#define HAS_MY_HEADER 1
#else
#define HAS_MY_HEADER 0
#endif
// ⽰例3:检查是否⽀持⽂件系统相关库
#if __has_include(<filesystem>)
#include <filesystem>
namespace fs = std::filesystem;
#elif __has_include(<experimental/filesystem>)
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
#error "需要 filesystem ⽀持"
#endif
int main() {
std::cout << "Optional support: " << HAS_OPTIONAL << std::endl;
std::cout << "My header support: " << HAS_MY_HEADER << std::endl;
// 实际使⽤
#if HAS_OPTIONAL
std::optional<int> opt = 42;
std::cout << "Optional value: " << *opt << std::endl;
#else
std::cout << "Optional not available" << std::endl;
#endif
return 0;
}
12. 属性
• 属性是为代码实体(变量、函数、类等)添加额外信息的⼀种机制,这些信息可以帮助编译器进⾏ 优化、⽣成警告或错误,以及指导静态分析⼯具。
• 在 C++11 之前,各编译器使⽤⾃⼰的语法。
// GCC/Clang:
__attribute__((noreturn)) void func() {}
__attribute__((deprecated)) void old_func() {}
// MSVC
__declspec(noreturn) void func() {}
__declspec(deprecated) void old_func() {}
// C++以后,使⽤统⼀的标准库属性
[[noreturn]] void func() {}
[[deprecated]] void old_func() {} // 注意:deprecated是C++14加⼊的
C++11将属性语法标准化引⼊了 [[ ]] 语法,但只提供了两个标准属性。C++14/C++17/C++20
中属性逐步丰富化。
\[noreturn\]\] (C++11),指⽰函数不会返回到它的调⽤点,主要⽤于优化和错误检测。通过明
确告知编译器函数不会返回,编译器可以优化代码逻辑,例如跳过某些⽆效的后续代码执⾏等。标
记为 noreturn 的函数返回时,编译器会提⽰警告。
•
\[\[carries_dependency\]\] (C++11),⾼级并发属性,它是为专家级并发优化提供的⼯具,
但对于 99.9% 的开发者来说,使⽤ std::atomic 默认内存序或其他⾼级并发⼯具是更明智的
选择。
#include
• [[maybe_unused]] (C++17),⽤于抑制编译器对未使⽤实体发出的"未使⽤变量/参数"警
告。
#include <iostream>
// [[fallthrough]] 属性
void checkValue(int x) {
switch (x) {
case 1:
std::cout << "Case 1" << std::endl;
[[fallthrough]]; // 故意不break,明确告知编译器
case 2:
std::cout << "Case 2" << std::endl;
break;
default:
std::cout << "Default case" << std::endl;
}
}
// [[nodiscard]] 属性
[[nodiscard]] int computeSomethingImportant() {
return 42;
}
// [[maybe_unused]] 属性
void someFunction([[maybe_unused]] int unusedParam) {
// 当你确实有意定义⼀个可能不会使⽤的变量(例如,
// 条件编译、为了清晰的代码结构、或者为了将来扩展)时,避免编译器产⽣烦⼈的警告
[[maybe_unused]] int unusedVar = 10;
// 不会产⽣未使⽤变量的警告
}
int main() {
checkValue(1);
// 忽略[[nodiscard]]结果的警告
computeSomethingImportant(); // 编译器可能会警告
// 正确的⽅式是使⽤返回值
int result = computeSomethingImportant();
std::cout << "Result: " << result << std::endl;
someFunction(100);
checkValue(1);
return 0;
}
\[likely\]\] 和 \[\[unlikely\]\] (C++20),这两个属性是⽤于向编译器提供分⽀预测的提
⽰,帮助编译器优化代码,使其在条件分⽀更可能或更不可能发⽣时,⽣成效率更⾼的机器代码。
• 现代 CPU 采⽤流⽔线技术,可以预先获取和解码后续的指令。当遇到条件分⽀(如 if 或
switch )时,CPU 必须猜测(预测)会⾛哪条路径以便提前加载指令。如果猜错了,就需要清
空流⽔线,重新加载正确的指令,这会带来显著的性能惩罚(称为分⽀预测错误惩罚)。
• \[\[likely\]\] : 向编译器指⽰,它所在的代码路径(例如, if 语句的 true 分⽀,或
switch 语句的某个 case )在程序执⾏过程中⾮常可能被采⽤。 \[\[unlikely\]\] : 向编译器
指⽰,它所在的代码路径⾮常不可能被采⽤。准规定这些属性只是⼀个提⽰,编译器可以完全忽略
它们。编译器可能会根据⾃⼰的分析结果覆盖你的提⽰。
• 不要滥⽤: 只有在你有充分的理由证明某个分⽀确实存在极端的概率偏差时,才使⽤这些属性。 盲 ⽬使⽤可能会误导编译器,会适得其反,反⽽导致性能下降。
int process(int value) {
if (value > 0) [[likely]] {
// 这个分⽀在绝⼤多数情况下都会执⾏
return value * 2;
}
else [[unlikely]] {
// 错误处理路径,很少执⾏
//log_error("Invalid value");
return -1;
}
}
void func()
{
int status = 0;
std::cin >> status;
// 也可以⽤于 switch 语句
switch (status) {
case 1: [[likely]]
//handle_success();
break;
case 2: [[unlikely]]
//handle_error();
break;
default: [[unlikely]]
// handle_unknown();
break;
}
}
int main()
{
process(1);
func();
return 0;
}
\[\[no_unique_address\]\](C++20)⽤于优化类的内存布局。它告诉编译器:被修饰的⾮静态数据
空成员可能不需要在对象中拥有独⽴的地址空间,空成员就是⽆⾮静态成员变量的类对象。
• 当前的问题,在 C++ 中,任何对象(即使是空类 class Empty {}; 的对象)都必须拥有⼀个
唯⼀的地址。这意味着它的 sizeof ⾄少为 1(通常为 1),以确保在数组中,每个对象都能被
区分开。当你有⼀个类( struct 或 class )包含多个空类类型的成员时,每个空成员都会⾄
少占⽤ 1 字节,再加上内存对⻬的填充字节,会导致⼤量内存浪费。
• 空基类优化 (EBO - Empty Base Optimization),C++ 标准允许编译器对空基类进⾏优化。如果⼀
个类继承⾃空基类,编译器可以不为基类⼦对象分配任何空间,使其⼤⼩为零。
• \[\[no_unique_address\]\] 的类似 EBO的优化 ,它可以应⽤于⾮静态数据成员,提⽰编译
器:如果该成员是空的,则不需要为其分配⼀个唯⼀的地址。它允许这个成员与类中的其他⾮静态
成员共享地址空间。
• 要注意如果是相同类型的多个空成员,这个优化可能会失效。
• vs2019和vs2022均未验证出这⾥的优化效果,gcc9.0验证出了这⾥的优化效果。
块struct Empty1 {};
struct Empty2 {};
struct Foo {
int x; // 4 bytes
[[no_unique_address]] Empty1 e1; // 可能被优化掉
[[no_unique_address]] Empty2 e2; // 可能被优化掉
};
// 空基类优化 (EBO - Empty Base Optimization)
struct Fxx : Empty1 {
int x;
};
struct Fyy : Empty1, Empty2 {
int y;
};
// 实践使⽤场景
template