C++17特性(3)

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 #include #include // 1. 终⽌程序的函数 [[noreturn]] void fatal_error(const std::string& message) { std::cerr << "Fatal error: " << message << std::endl; std::exit(EXIT_FAILURE); // 不需要 return 语句 } // 2. 总是抛出异常的函数 [[noreturn]] void throw_runtime_error(const char* message) { throw std::runtime_error(message); // 不会执⾏到这⾥ } // 3. ⽆限循环的函数 [[noreturn]] void event_loop() { while (true) { // 处理事件... } // 不会执⾏到这⾥ } // 编译器优化⽰例 int main() { fatal_error("error exit"); } \[\[deprecated\]\] (C++14),它⽤于标记某个实体(如函数、类、变量、类型等)已被弃⽤,不 建议再使⽤。当编译器遇到使⽤被标记为 deprecated 的实体时,会发出编译 #include // 标记⼀个函数为弃⽤ [[deprecated]] void old_function(); // 可以提供⼀条⾃定义的警告信息 [[deprecated("Use the new_function() instead, which is safer and faster.")]] void legacy_function(); // 也可以标记类、类型别名、变量等 [[deprecated]] typedef char* CString; struct [[deprecated]] OldStruct {}; [[deprecated]] int obsolete_variable; [[deprecated("This function is prone to errors. Use calculate() instead.")]] int compute() { return 42; } // deprecated也可以⽤于类型、变量、枚举等 // v1.0 [[deprecated("这是v1.0版本,请使⽤v2.0改⽤connect(url, options)")]] void connect(std::string url) {} // v2.0 void connect(std::string url, int options) {} int main() { int x = compute(); // 编译时会产⽣警告或错误 std::cout << x << std::endl; CString ptr = nullptr; connect("https://en.cppreference.com/w/cpp/language/attributes.html"); return 0; } C++17 对属性系统进⾏了更重⼤的扩展,引⼊了三个⾮常重要的新属性,并极⼤地扩展了属性的应 ⽤范围。 • \[\[fallthrough\]\] (C++17), ⽤于在 switch 语句中显式地表明从⼀个 case 标签"贯 穿"到下⼀个 case 标签是有意为之的,以避免编译器发出警告。 • \[\[nodiscard\]\] (C++17),⽤于标记⼀个函数的返回值⾮常重要,调⽤者不应该忽略它。如果 调⽤者没有使⽤返回值,编译器会发出警告。C++20 允许添加原因信息: \[\[nodiscard("xxx")\]

• [[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> class Vector { private: int* data; size_t size; size_t capacity; [[no_unique_address]] Allocator alloc; // allocator没有⾮静态成员变量 }; int main() { std::cout << sizeof(Empty1) << std::endl; std::cout << sizeof(Foo) << std::endl; // EBO可以优化⼀个空基类,多个空基类就不⾏了 std::cout << sizeof(Fxx) << std::endl; std::cout << sizeof(Fyy) << std::endl; std::cout << sizeof(std::allocator) << std::endl; return 0; }

相关推荐
晨非辰2 小时前
Linux权限实战速成:用户切换/文件控制/安全配置15分钟掌握,解锁核心操作与权限模型内核逻辑
linux·运维·服务器·c++·人工智能·后端
煤炭里de黑猫2 小时前
Python 爬虫进阶:利用 Frida 逆向移动端 App API 以实现高效数据采集
开发语言·爬虫·python
草莓熊Lotso2 小时前
Linux 进程创建与终止全解析:fork 原理 + 退出机制实战
linux·运维·服务器·开发语言·汇编·c++·人工智能
枫叶丹42 小时前
【Qt开发】Qt系统(九)-> Qt TCP Socket
c语言·开发语言·网络·c++·qt·tcp/ip
007php0074 小时前
PHP与Java项目在服务器上的对接准备与过程
java·服务器·开发语言·分布式·面试·职场和发展·php
Evand J5 小时前
【MATLAB程序,一维非线性EKF与RTS】MATLAB,用于一维的位移与速度滤波和RTS平滑/高精度定位,带滤波前后的误差对比
开发语言·matlab·卡尔曼滤波·rts平滑·正向滤波
火云洞红孩儿10 小时前
告别界面孤岛:PyMe如何用一站式流程重塑Python GUI开发?
开发语言·python
叫我辉哥e110 小时前
新手进阶Python:办公看板集成ERP跨系统同步+自动备份+AI异常复盘
开发语言·人工智能·python
小丑西瓜66610 小时前
CMake基础用法,cmake_minimum_required,project,add_executable
linux·服务器·c++·camke