目录
构造函数模板推导
结构化绑定
if-switch语句初始化
内联变量
折叠表达式
constexpr lambda表达式
namespace嵌套
__has_include预处理表达式
在lambda表达式用*this捕获对象副本
新增Attribute
字符串转换
std::variant
std::optional
std::any
std::apply
std::make_from_tuple
std::string_view
as_const
file_system
C++17就不需要特殊指定,直接可以推导出类型,代码如下:
pair p(1, 2.2); // c++17 自动推导
vector v = {1, 2, 3}; // c++17
通过结构化绑定,对于tuple、map等类型,获取相应值会方便很多,看代码:
std::tuple func() {
return std::tuple(1, 2.2);
}
int main() {
auto[i, d] = func(); //是C++11的tie吗?更高级
cout << i << endl;
cout << d << endl;
}
//==========================
void f() {
map m = {
{0, "a"},
{1, "b"},
};
for (const auto &[i, s] : m) {
cout << i << " " << s << endl;
}
}
// ====================
int main() {
std::pair a(1, 2.3f);
auto[i, f] = a;
cout << i << endl; // 1
cout << f << endl; // 2.3f
return 0;
}
结构化绑定还可以改变对象的值,使用引用即可:
// 进化,可以通过结构化绑定改变对象的值
int main() {
std::pair a(1, 2.3f);
auto& [i, f] = a;
i = 2;
cout << a.first << endl; // 2
}
注意结构化绑定不能应用于constexpr
constexpr auto[x, y] = std::pair(1, 2.3f); // compile error, C++20可以
结构化绑定不止可以绑定pair和tuple,还可以绑定数组和结构体等
int array[3] = {1, 2, 3};
auto [a, b, c] = array;
cout << a << " " << b << " " << c << endl;
// 注意这里的struct的成员一定要是public的
struct Point {
int x;
int y;
};
Point func() {
return {1, 2};
}
const auto [x, y] = func();
这里其实可以实现自定义类的结构化绑定,代码如下:
// 需要实现相关的tuple_size和tuple_element和get方法。
class Entry {
public:
void Init() {
name_ = "name";
age_ = 10;
}
std::string GetName() const { return name_; }
int GetAge() const { return age_; }
private:
std::string name_;
int age_;
};
template
auto get(const Entry& e) {
if constexpr (I == 0) return e.GetName();
else if constexpr (I == 1) return e.GetAge();
}
namespace std {
template<> struct tuple_size : integral_constant {};
template<> struct tuple_element<0, Entry> { using type = std::string; };
template<> struct tuple_element<1, Entry> { using type = int; };
}
int main() {
Entry e;
e.Init();
auto [name, age] = e;
cout << name << " " << age << endl; // name 10
return 0;
}
// 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++类的静态成员变量在头文件中是不能初始化的,但是有了内联变量,就可以达到此目的:
// header file
struct A {
static const int value;
};
inline int const A::value = 10;
// ==========或者========
struct A {
inline static const int value = 10;
}
C++17引入了折叠表达式使可变参数模板编程更方便:
template
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
C++17前lambda表达式只能在运行时使用,C++17引入了constexpr lambda表达式,可以用于在编译期进行计算。
int main() { // c++17可编译
constexpr auto lamb = [] (int n) { return n * n; };
static_assert(lamb(3) == 9, "a");
}
函数体不能包含汇编语句、goto语句、label、try块、静态变量、线程局部存储、没有初始化的普通变量,不能动态分配内存,不能有new delete等,不能虚函数。
namespace A {
namespace B {
namespace C {
void func();
}
}
}
// c++17,更方便更舒适
namespace A::B::C {
void func();)
}
可以判断是否有某个头文件,代码可能会在不同编译器下工作,不同编译器的可用头文件有可能不同,所以可以使用此来判断:
#if defined __has_include
#if __has_include()
#define has_charconv 1
#include
#endif
#endif
std::optional 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,但是这里捕获的是this指针,指向的是对象的引用,正常情况下可能没问题,但是如果多线程情况下,函数的作用域超过了对象的作用域,对象已经被析构了,还访问了成员变量,就会有问题。所以C++17增加了新特性,捕获*this,不持有this指针,而是持有对象的拷贝,这样生命周期就与对象的生命周期不相关啦。
struct A {
int a;
void func() {
auto f = [*this] { // 这里
cout << a << endl;
};
f();
}
};
int main() {
A a;
a.func();
return 0;
}
[[fallthrough]],用在switch中提示可以直接落下去,不需要break,让编译期忽略警告
switch (i) {}
case 1:
xxx; // warning
case 2:
xxx;
[[fallthrough]]; // 警告消除
case 3:
xxx;
break;
}
[[nodiscard]] :表示修饰的内容不能被忽略,可用于修饰函数,标明返回值一定要被处理
[[nodiscard]] int func();
void F() {
func(); // warning 没有处理函数返回值
}
[[maybe_unused]] :提示编译器修饰的内容可能暂时没有使用,避免产生警告
void func1() {}
[[maybe_unused]] void func2() {} // 警告消除
void func3() {
int x = 1;
[[maybe_unused]] int y = 2; // 警告消除
}
新增from_chars函数和to_chars函数,直接看代码:
to_chars:通过成功填充范围 [first, last) ,转换 value 为字符串,要求 [first, last) 是合法范围。
from_chars:若无字符匹配模式或若按照分析匹配字符获得的值不能以 value 的类型表示,则不修改 value ,否则将匹配模式的字符转译成算术值的文本表示,并将值存储于 value 。
#include
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
}
C++17增加std::variant实现类似union的功能,但却比union更高级,举个例子union里面不能有string这种类型,但std::variant却可以,还可以支持更多复杂类型,如map等
int main() { // c++17可编译
std::variant var("hello");
cout << var.index() << endl;
var = 123;
cout << var.index() << endl;
try {
var = "world";
std::string str = std::get(var); // 通过类型获取值
var = 3;
int i = std::get<0>(var); // 通过index获取对应值
cout << str << endl;
cout << i << endl;
} catch(...) {
// xxx;
}
return 0;
}
一般情况下variant的第一个类型一般要有对应的构造函数,否则编译失败:
如何避免这种情况呢,可以使用std::monostate来打个桩,模拟一个空状态。
std::variant var; // 可以编译成功
我们有时候可能会有需求,让函数返回一个对象,如下:
struct A {};
A func() {
if (flag) return A();
else {
// 异常情况下,怎么返回异常值呢,想返回个空呢
}
}
有一种办法是返回对象指针,异常情况下就可以返回nullptr啦,但是这就涉及到了内存管理,也许你会使用智能指针,但这里其实有更方便的办法就是std::optional。
std::optional StoI(const std::string &s) {
try {
return std::stoi(s);
} catch(...) {
return std::nullopt;
}
}
void func() {
std::string s{"123"};
std::optional o = StoI(s);
if (o) {
cout << *o << endl;
} else {
cout << "error" << endl;
}
}
C++17引入了any可以存储任何类型的单个值,见代码:
int main() { // c++17可编译
std::any a = 1;
cout << a.type().name() << " " << std::any_cast(a) << endl;
a = 2.2f;
cout << a.type().name() << " " << std::any_cast(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(a) << endl;
return 0;
}
使用std::apply可以将tuple展开作为函数的参数传入,见代码:
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';
}
使用make_from_tuple可以将tuple展开作为构造函数参数
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(std::move(tuple));
}
通常我们传递一个string时会触发对象的拷贝操作,大字符串的拷贝赋值操作会触发堆内存分配,很影响运行效率,有了string_view就可以避免拷贝操作,平时传递过程中传递string_view即可。
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;
}
C++17使用as_const可以将左值转成const类型
std::string str = "str";
const std::string& constStr = std::as_const(str);
C++17正式将file_system纳入标准中,提供了关于文件的大多数功能,基本上应有尽有,这里简单举几个例子:
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);
C++17引入了shared_mutex,可以实现读写锁