C++ 17 新特性

构造函数模板推导

在 C++17 前构造一个模板类对象需要指明类型,C++17 就不需要特殊指定,直接可以推导出类型

1
2
3
4
pair<int, double> p(1, 2.2); // before c++17

pair p(1, 2.2); // c++17 自动推导
vector v = {1, 2, 3}; // c++17

结构化绑定

if-switch 语句初始化

1
2
3
4
5
6
7
8
9
10
// if (init; condition)

if (int a = GetValue()); a < 101) {
cout << a;
}

string str = "Hi World";
if (auto [pos, size] = pair(str.find("Hi"), str.size()); pos != string::npos) {
std::cout << pos << " Hello, size is " << size;
}

内联变量

C++17 前只有内联函数,现在有了内联变量,C++ 类的静态成员变量在头文件中是不能初始化的,但是有了内联变量,就可以达到此目的

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

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

折叠表达式

C++17 引入了折叠表达式使可变参数模板编程更方便

1
2
3
4
5
6
7
8
template <typename ... Ts>
auto sum(Ts ... ts) {
return (ts + ...);
}
int a {sum(1, 2, 3, 4, 5)}; // 15
std::string a{"hello "};
std::string b{"world"};
cout << sum(a, b) << endl; // hello world

constexpr lambda表达式

C++17 前lambda 表达式只能在运行时使用,C++17 引入了 constexpr lambda 表达式,可以用于在编译期进行计算

1
2
3
4
int main() { // c++17 可编译
constexpr auto lamb = [] (int n) { return n * n; };
static_assert(lamb(3) == 9, "a");
}

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

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();)
}

__has_include预处理表达式

可以判断是否有某个头文件,代码可能会在不同编译器下工作,不同编译器的可用头文件有可能不同,所以可以使用此来判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#if defined __has_include
#if __has_include(<charconv>)
#define has_charconv 1
#include <charconv>
#endif

std::optional<int> ConvertToInt(const std::string& str) {
int value{};
#ifdef has_charconv
const auto last = str.data() + str.size();
const auto res = std::from_chars(str.data(), last, value);
if (res.ec == std::errc{} && res.ptr == last) return value;
#else
// alternative implementation...
其它方式实现
#endif
return std::nullopt;
}

lambda 表达式用 *this 捕获对象副本

正常情况下,lambda表达式中访问类的对象成员变量需要捕获 this,但是这里捕获的是this 指针,指向的是对象的引用,正常情况下可能没问题,但是如果多线程情况下,函数的作用域超过了对象的作用域,对象已经被析构了,还访问了成员变量,就会有问题

所以 C++17 增加了新特性,捕获 *this,不持有this 指针,而是持有对象的拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct A {
int a;
void func() {
auto f = [*this] { // 这里
cout << a << endl;
};
f();
}
};
int main() {
A a;
a.func();
return 0;
}

新增 Attribute

在项目中见过 declspec__attribute#pragma 指示符,使用它们来给编译器提供一些额外的信息,来产生一些优化或特定的代码,也可以给其它开发者一些提示信息

1
2
struct A { short f[3]; } __attribute__((aligned(8)));
void fatal() __attribute__((noreturn));

在 C++11 和 C++14 中有更方便的方法

  • [[carries_dependency]]让编译期跳过不必要的内存栅栏指令
  • [[noreturn]]函数不会返回
  • [[deprecated]]函数将弃用的警告
  • [[noreturn]] void terminate() noexcept;
  • [[deprecated("use new func instead")]] void func() {}

C++17 又新增了三个

  • [[fallthrough]]:用在 switch 中提示可以直接落下去,不需要 break,让编译期忽略警告

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    switch (i) {}
    case 1:
    xxx; // warning
    case 2:
    xxx;
    [[fallthrough]]; // 警告消除
    case 3:
    xxx;
    break;
    }

    使得编译器和其它开发者都可以理解开发者的意图

  • [[nodiscard]]:表示修饰的内容不能被忽略,可用于修饰函数,标明返回值一定要被处理

    1
    2
    3
    4
    [[nodiscard]] int func();
    void F() {
    func(); // warning 没有处理函数返回值
    }
  • [[maybe_unused]]:提示编译器修饰的内容可能暂时没有使用,避免产生警告

    1
    2
    3
    4
    5
    6
    void func1() {}
    [[maybe_unused]] void func2() {} // 警告消除
    void func3() {
    int x = 1;
    [[maybe_unused]] int y = 2; // 警告消除
    }

字符串转换

新增from_chars 函数和 to_chars 函数

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

int main() {
const std::string str{"123456098"};
int value = 0;
const auto res = std::from_chars(str.data(), str.data() + 4, value);
if (res.ec == std::errc()) {
cout << value << ", distance " << res.ptr - str.data() << endl;
} else if (res.ec == std::errc::invalid_argument) {
cout << "invalid" << endl;
}
str = std::string("12.34);
double val = 0;
const auto format = std::chars_format::general;
res = std::from_chars(str.data(), str.data() + str.size(), value, format);

str = std::string("xxxxxxxx");
const int v = 1234;
res = std::to_chars(str.data(), str.data() + str.size(), v);
cout << str << ", filled " << res.ptr - str.data() << " characters \n";
// 1234xxxx, filled 4 characters
}

std::variant

C++17 增加 std::variant 实现类似 union 的功能,但却比 union 更高级,举个例子 union 里面不能有 string 这种类型,但 std::variant 却可以,还可以支持更多复杂类型,如 map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main() { // c++17 可编译
std::variant<int, std::string> var("hello");
cout << var.index() << endl;
var = 123;
cout << var.index() << endl;

try {
var = "world";
std::string str = std::get<std::string>(var); // 通过类型获取值
var = 3;
int i = std::get<0>(var); // 通过 index 获取对应值
cout << str << endl;
cout << i << endl;
} catch(...) {
// xxx;
}
return 0;
}

一般情况下 variant 的第一个类型一般要有对应的构造函数,否则编译失败

1
2
3
4
5
6
struct A {
A(int i){}
};
int main() {
std::variant<A, int> var; // 编译失败
}

可以使用 std::monostate 来打个桩,模拟一个空状态

1
std::variant<std::monostate, A> var; // 可以编译成功

std::optional

有时候可能会有需求,让函数返回一个对象,如下:

1
2
3
4
5
6
7
struct A {};
A func() {
if (flag) return A();
else {
// 异常情况下,怎么返回异常值呢,想返回个空呢
}
}

有一种办法是返回对象指针,异常情况下就可以返回nullptr,但是这就涉及到了内存管理,也许会使用智能指针,但这里其实有更方便的办法就是std::optional

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
std::optional<int> StoI(const std::string &s) {
try {
return std::stoi(s);
} catch(...) {
return std::nullopt;
}
}

void func() {
std::string s{"123"};
std::optional<int> o = StoI(s);
if (o) {
cout << *o << endl;
} else {
cout << "error" << endl;
}
}

std::any

C++17 引入了 any 可以存储任何类型的单个值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main() { // c++17 可编译
std::any a = 1;
cout << a.type().name() << " " << std::any_cast<int>(a) << endl;
a = 2.2f;
cout << a.type().name() << " " << std::any_cast<float>(a) << endl;
if (a.has_value()) {
cout << a.type().name();
}
a.reset();
if (a.has_value()) {
cout << a.type().name();
}
a = std::string("a");
cout << a.type().name() << " " << std::any_cast<std::string>(a) << endl;
return 0;
}

std::apply

使用 std::apply 可以将 tuple 展开作为函数的参数传入

1
2
3
4
5
6
7
8
9
int add(int first, int second) { return first + second; }

auto add_lambda = [](auto first, auto second) { return first + second; };

int main() {
std::cout << std::apply(add, std::pair(1, 2)) << '\n';
std::cout << add(std::pair(1, 2)) << "\n"; // error
std::cout << std::apply(add_lambda, std::tuple(2.0f, 3.0f)) << '\n';
}

std::make_from_tuple

使用 make_from_tuple 可以将 tuple 展开作为构造函数参数

1
2
3
4
5
6
7
8
9
struct Foo {
Foo(int first, float second, int third) {
std::cout << first << ", " << second << ", " << third << "\n";
}
};
int main() {
auto tuple = std::make_tuple(42, 3.14f, 0);
std::make_from_tuple<Foo>(std::move(tuple));
}

std::string_view

通常传递一个 string 时会触发对象的拷贝操作,大字符串的拷贝赋值操作会触发堆内存分配,很影响运行效率,有了 string_view 就可以避免拷贝操作,平时传递过程中传递 string_view 即可

1
2
3
4
5
6
7
8
9
10
11
void func(std::string_view stv) { cout << stv << endl; }

int main(void) {
std::string str = "Hello World";
std::cout << str << std::endl;

std::string_view stv(str.c_str(), str.size());
cout << stv << endl;
func(stv);
return 0;
}

as_const

C++17 使用 as_const 可以将左值转成 const 类型

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

file_system

C++17 正式将 file_system 纳入标准中,提供了关于文件的大多数功能

1
2
3
4
5
namespace fs = std::filesystem;
fs::create_directory(dir_path);
fs::copy_file(src, dst, fs::copy_options::skip_existing);
fs::exists(filename);
fs::current_path(err_code);

std::shared_mutex

C++17 引入了shared_mutex,可以实现读写锁


C++ 17 新特性
https://silhouettesforyou.github.io/2022/02/22/84e21c0b6a1a/
Author
Xiaoming
Posted on
February 22, 2022
Licensed under