logo头像
Snippet 博客主题

c++17新特性

1. 构造函数模板推导

在C++17之前构造一个模板类对象需要指明类型:

1
pair<int, double> p(1, 2,2);

从C++17开始,构造模板类对象可以根据值自行推导了,如下:

1
2
3
// 在vs2022上实测还是不行,还是需要指明类型
pair p(1, 2,2);
vector v = {1, 2, 3};

2. 结构化绑定

对于tuplemappair、数组、结构体类型,获取相应的值更方便了。看示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;

std::tuple<int, double> func()
{
return std::tuple(1, 2.2); // 构造函数模板推导
}

int main()
{
auto[i, d] = func(); // 结构化绑定
cout << i << endl; // 1
cout << d << endl; // 2.2
}

结构化绑定不仅可以取对象值,还可以改变对象的值,使用引用即可。

1
2
3
4
std::pair a(1, 2.3f);
auto& [i, f] = a; // auto& 绑定可以改变对象的值
i = 2;
cout << a.first << endl; // 2

3. if-switch语句初始化

C++17可以在if-switch语句中初始化变量,这样可以尽可能约束作用域,让代码更简洁,可能代码可读性略有下降,但是还好。

1
2
3
4
// 格式:if(init; condition)
if (int a = getValue(); a > 0) {
cout << a;
}

4. 内联变量

C++17前只有内联函数,现在新加了内联变量(头文件中也可以定义变量了),这样像以前静态成员变量必须在cpp中初始化的限制也没有了,成员变量可以直接在头文件也可以初始化了。

1
2
3
4
5
6
7
8
9
10
// 头文件
struct A {
static const int value;
};
inline int const A::value = 10;

// ==========或者========
struct A {
inline constexpr int value = 10;
}

引入内联变量好处:

  • 简化代码,允许在头文件中定义变量,无需实现文件中进行定义,减少代码重复和简化项目结构。
  • 避免链接错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 这个在链接时就会报重定义错误
// myvar.h
extern int myVar;

// file1.cpp
#include "myvar.h"
int myVar = 10;

// file2.cpp
#include "myvar.h"
int myVar = 20;

// 如果用内联变量就可以直接在公共头文件中定义了
// myvar.h
inline int myVar = 10; // 内联变量的定义

// file1.cpp
#include "myvar.h"

// file2.cpp
#include "myvar.h"
  • 提高编译时性能,就如上面的示例,变量定义在头文件中,编译器在编译时可以直接把定义插入到使用它的代码中,而不需要进行链接,减少链接开销。

5. 折叠表达式

C++17引入了折叠表达式,在使用可变参数模板中更加方便了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;

// 参数相加函数模板
template <typename... Ts>
auto sum(Ts... ts)
{
return (ts + ...); // 可变参数折叠起来了
}

int main()
{
//int a { sum(1, 2, 3, 4, 5) }; // 15

std::string a { "hello " };
std::string b { "world" };
cout << sum(a, b) << endl; // hello world
}

6. constexpr lambda表达式

C++17之前lambda表达式只能在运行时计算,C++17引入了constexpr lambda表达式,可以在编译期就进行计算,提高运行效率。

1
2
3
4
5
6
7
int main()
{
constexpr auto lamb = [](int n) { return n * n; };
if (lamb(3) == 9) {
cout << "true" << endl;
}
}

注意:constexpr函数有如下限制:

函数体不能包含汇编语句、goto语句、label、try块、静态变量、线程局部存储、没有初始化的普通变量,不能动态分配内存,不能有new delete等,不能虚函数。

7. namespace嵌套

C++17开始标准化支持namespace嵌套的写法,更方便简洁了。

1
2
3
4
5
6
7
8
9
10
11
12
namespace A {
namespace B {
namespace C {
void func();
}
}
}

// c++17,更方便更舒适
namespace A::B::C {
void func();)
}

8. __has_include预处理表达式

C++17引入了__has_include,是一个预处理器指令,用于在编译时检查某个头文件是否存在。

使用__has_include可以让我们在代码中动态地决定是否包含某个头文件,这对于跨平台开发和库的兼容性非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>  

int main() {
if (__has_include("optional.h")) {
// 如果存在"optional.h"文件
std::cout << "Optional header is available." << std::endl;
// 包含"optional.h"文件
#include "optional.h"
} else {
// 如果不存在"optional.h"文件
std::cout << "Optional header is not available." << std::endl;
}
return 0;
}

注:在vs2022上,语言集也设置到了c++17,自测报:error C3861: “__has_include”: 找不到标识符!

9. 支持lambda表达式用*this获取对象副本

C++17以前lambda只支持传递this引用,修改会导致本体也被修改。

1
2
3
4
5
6
7
8
9
struct A {
int a;
void func() {
auto f = [this] { // 对象引用
cout << a << endl;
};
f();
}
};

所以现在扩展了,支持传递*this,传递的是对象的拷贝副本。

1
2
3
4
5
6
7
8
9
struct A {
int a;
void func() {
auto f = [*this] { // 对象副本
cout << a << endl;
};
f();
}
};

10.新增Attribute

C++17又新增了3个属性:

  • [[fallthrough]] 用在switch语句中,显示表明接下执行下一个case,而不是break跳出。不写break其实是同样的效果,它只是增加可读性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>

int main()
{
int num = 1;

switch (num) {
case 1:
std::cout << "Number is 1." << std::endl;
[[fallthrough]]; // 显式指示继续执行下一个case的代码
case 2:
std::cout << "Number is 2." << std::endl;
break;
case 3:
std::cout << "Number is 3." << std::endl;
break;
default:
std::cout << "Number is not 1, 2, or 3." << std::endl;
break;
}

return 0;
}

// Number is 1.
// Number is 2.
  • [[nodiscard]] 修饰普通函数的返回值,用于向编译器发出警告,要求调用函数的返回值不被忽略。这个属性标记可以帮助开发人员避免忽略可能产生错误或潜在问题的函数返回值。只是一个编译警告,不会强制要求调用者使用函数的返回值。
1
2
3
4
5
6
7
8
9
[[nodiscard]] int calculateSum(int a, int b) {
return a + b;
}

int main() {
calculateSum(3, 4); // 警告:函数返回值被忽略

return 0;
}
  • [[maybe_unused]] 用于告诉编译器可以忽略某个实体(如变量、函数、参数等)未使用的警告。这个属性标记可以用于减少编译器产生的未使用实体的警告信息。
1
2
3
4
5
6
7
8
9
[[maybe_unused]] void unusedFunction() {
// 函数体
}

int main() {
[[maybe_unused]] int unusedVariable = 5;

return 0;
}

11. 字符串与基本类型相互转换

C++17新增了from_chars函数和to_chars函数,它们提供了一种高效的方式来解析字符串并将其转换为基本类型,或者将基本类型转换为字符序列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 将字符串转换为整型
#include <charconv>
#include <iostream>

int main() {
const char* str = "12345";
int value = 0;
// 参数1:待转换的字符串指针
// 参数2:待转换字符串末尾指针
// 参数3:目标变量的引用
auto result = std::from_chars(str, str + std::strlen(str), value);
if (result.ec == std::errc()) {
std::cout << "Parsed value: " << value << std::endl;
} else {
std::cout << "Parsing failed." << std::endl;
}

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 将整数转换为字符串
#include <charconv>
#include <iostream>

int main() {
int value = 12345;
char buffer[20];
// 参数1:指向要写入转换结果的字符串的指针
// 参数2:指向字符串末尾位置的指针
// 参数3:要转换的数据类型的值
auto result = std::to_chars(buffer, buffer + sizeof(buffer), value);
if (result.ec == std::errc()) {
*result.ptr = '\0'; // 添加字符串结尾的空字符
std::cout << "Converted value: " << buffer << std::endl;
} else {
std::cout << "Conversion failed." << std::endl;
}

return 0;
}

12. std::variant

std::variant是C++17引入的标准库模板类,它提供了一种类型安全的、高效的联合类型的实现。相比传统C语言的union,它更灵活,更安全,支持的类型更多。

使用std::variant时,需要在模板参数中指定可能的类型。例如,std::variant<int, double, std::string>表示一个可以容纳intdoublestd::string类型中的一个值的std::variant对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <variant>
#include <iostream>
#include <string>

int main() {
std::variant<int, double, std::string> var;

var = 42; // 存储int类型的值
std::cout << std::get<int>(var) << std::endl;

var = 3.14; // 存储double类型的值
std::cout << std::get<double>(var) << std::endl;

var = "Hello"; // 存储std::string类型的值
std::cout << std::get<std::string>(var) << std::endl;

return 0;
}

13. std::optional

std::optional是C++17引入的标准库模板类,它提供了一种表示可能存在或不存在值的对象。在函数返回不存在的异常值情况时,配合std::nullopt使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <optional>
#include <iostream>

std::optional<int> divide(int a, int b) {
if (b != 0) {
return a / b;
} else {
return std::nullopt; // 表示没有值
}
}

int main() {
std::optional<int> result = divide(10, 5);
if (result) {
std::cout << "Result: " << *result << std::endl;
} else {
std::cout << "Division by zero" << std::endl;
}

return 0;
}

14. std::any

std::any是C++17引入的标准库模板类,它提供了一种类型安全的机制来存储和访问任意类型的值。使用std::any时,可以将任意类型的值存储在std::any对象中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <any>
#include <iostream>
#include <string>

int main() {
std::any value;

value = 42; // 存储整数
std::cout << std::any_cast<int>(value) << std::endl; // 输出: 42

value = 3.14; // 存储浮点数
std::cout << std::any_cast<double>(value) << std::endl; // 输出: 3.14

value = std::string("Hello"); // 存储字符串
std::cout << std::any_cast<std::string>(value) << std::endl; // 输出: Hello

return 0;
}

15. std::apply

std::apply是C++17引入的标准库函数,用于将参数包展开并应用到函数中。它提供了一种方便的方式来调用函数,将参数包作为函数的参数传递,而不需要显式地编写参数列表。(使用std::tuple打包参数给普通函数传参)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <tuple>
#include <functional>

void print_values(int a, float b, const std::string& c) {
std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl;
}

int main() {
std::tuple<int, float, std::string> values(42, 3.14, "Hello");

std::apply(print_values, values); // apply会自动把values展开传递给print_values函数参数

return 0;
}

16. std::make_from_tuple

std::make_from_tuple是C++17引入的标准库函数,std::make_from_tuple会使用std::tuple中的元素作为某个类的构造函数的参数,构造这个对象并返回。(使用std::tuple打包参数给构造函数传参)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <tuple>
#include <string>

struct Person {
std::string name;
int age;

Person(const std::string& name, int age) : name(name), age(age) {}

void print_info() {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
};

int main() {
std::tuple<std::string, int> person_info("Alice", 25);

auto person = std::make_from_tuple<Person>(person_info);

person.print_info();

return 0;
}

17. std::string_veiw

std::string_view是C++17引入的标准库类型,它提供了一种轻量级的方式来操作字符串数据,而无需复制字符串内容。它本质上是一个指向字符串数据的指针和长度的组合,它并不拥有字符串的存储空间。因此,它适用于需要对字符串进行读取操作而不需要修改字符串的场景。

这样传递字符串参数使用string_view对象传递,就可以避免字符串拷贝操作了,提高运行效率。(其实和传递&string引用差不多,只是另一种写法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <string_view>

void print_string_view(std::string_view str) {
std::cout << "String view: " << str << std::endl;
std::cout << "Length: " << str.length() << std::endl;
}

int main() {
std::string str = "Hello, world!";
std::string_view str_view(str); // 将string对象用string_view包装

print_string_view(str_view);

return 0;
}

18. as_const

std::as_const是一个 C++17 引入的函数模板,用于将一个对象转换为 const 引用。它的作用是确保无法通过该 const 引用对对象进行修改,即使原本对象是非 const 的。

1
2
std::string str = "hello world";
const std::string& constStr = std::as_const(str);

19. file_system

std::filesystem 是 C++17 引入的标准库,用于操作文件系统和文件路径。它提供了一组类和函数,用于处理文件、目录、路径等相关操作。

要使用 std::filesystem,需要包含 <filesystem> 头文件,并使用 std::filesystem 命名空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <filesystem>

namespace fs = std::filesystem; // 对命名空间取别名

int main() {
// 检查文件是否存在
fs::path filePath = "path/to/file.txt";
if (fs::exists(filePath)) {
std::cout << "File exists." << std::endl;
} else {
std::cout << "File does not exist." << std::endl;
}

// 创建目录
fs::path dirPath = "path/to/directory";
if (fs::create_directory(dirPath)) {
std::cout << "Directory created." << std::endl;
} else {
std::cout << "Failed to create directory." << std::endl;
}

// 遍历目录中的文件
fs::path directory = "path/to/directory";
for (const auto& entry : fs::directory_iterator(directory)) {
std::cout << entry.path() << std::endl;
}

// 获取文件大小
fs::path file = "path/to/file.txt";
if (fs::exists(file) && fs::is_regular_file(file)) {
std::cout << "File size: " << fs::file_size(file) << " bytes." << std::endl;
}

// 删除文件
fs::path fileToDelete = "path/to/file.txt";
if (fs::exists(fileToDelete) && fs::is_regular_file(fileToDelete)) {
if (fs::remove(fileToDelete)) {
std::cout << "File deleted." << std::endl;
} else {
std::cout << "Failed to delete file." << std::endl;
}
}

return 0;
}

20. std::shared_mutex

std::shared_mutex 是 C++14 引入的标准库,用于实现读写锁(Read-Write Lock)的功能。它提供了一种机制,允许多个线程同时读取共享数据,但只允许一个线程写入共享数据。

它和c++14的std::shared_timed_mutex 一样,只是少了超时功能,更加简洁简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <shared_mutex>
#include <thread>

std::shared_mutex mutex;
int sharedData = 0;

void writerThread() {
for (int i = 0; i < 5; ++i) {
std::unique_lock<std::shared_mutex> lock(mutex); // 获取独占的写锁
++sharedData;
std::cout << "Writer thread: wrote " << sharedData << std::endl;
}
}

void readerThread() {
for (int i = 0; i < 5; ++i) {
std::shared_lock<std::shared_mutex> lock(mutex); // 获取共享的读锁
std::cout << "Reader thread: read " << sharedData << std::endl;
}
}

int main() {
std::thread writer(writerThread);
std::thread reader(readerThread);

writer.join();
reader.join();

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Writer thread: wrote 1
Writer thread: wrote 2
Writer thread: wrote 3
Writer thread: wrote 4
Writer thread: wrote 5
Reader thread: read 5
Reader thread: read 5
Reader thread: read 5
Reader thread: read 5
Reader thread: read 5
或者=======================
Reader thread: read 0
Reader thread: read 0
Reader thread: read 0
Reader thread: read 0
Reader thread: read 0
Writer thread: wrote 1
Writer thread: wrote 2
Writer thread: wrote 3
Writer thread: wrote 4
Writer thread: wrote 5