共计 8109 个字符,预计需要花费 21 分钟才能阅读完成。
程序喵之前曾经介绍过 C ++11 的新个性和 C ++14 的新个性,链接如下:xxx,明天向敬爱的读者们介绍下 C ++17 的新个性,当初基本上各个编译器对 C ++17 都曾经提供齐备的反对,倡议大家编程中尝试应用下 C ++17,能够肯定水平上简化代码编写,进步编程效率。
次要新个性如下:
- 构造函数模板推导
- 结构化绑定
- if-switch 语句初始化
- 内联变量
- 折叠表达式
- constexpr lambda 表达式
- namespace 嵌套
- __has_include 预处理表达式
- 在 lambda 表达式用 *this 捕捉对象正本
- 新增 Attribute
- 字符串转换
- std::variant
- std::optional
- std::any
- std::apply
- std::make_from_tuple
- as_const
- std::string_view
- file_system
- std::shared_mutex
上面程序喵一一介绍:
构造函数模板推导
在 C ++17 前结构一个模板类对象须要指明类型:
pair<int, double> p(1, 2.2); // before c++17
C++17 就不须要非凡指定,间接能够推导出类型,代码如下:
pair p(1, 2.2); // c++17 主动推导
vector v = {1, 2, 3}; // c++17
结构化绑定
通过结构化绑定,对于 tuple、map 等类型,获取相应值会不便很多,看代码:
std::tuple<int, double> 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<int, string> 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<N> 办法。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 <size_t I>
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<Entry> : integral_constant<size_t, 2> {};
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-switch 语句初始化
C++17 前 if 语句须要这样写代码:
int a = GetValue();
if (a < 101) {cout << a;}
C++17 之后能够这样:
// 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 <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 表达式,能够用于在编译期进行计算。
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 嵌套
namespace A {
namespace B {
namespace C {void func();
}
}
}
// c++17,更不便更舒服
namespace A::B::C {void func();)
}
__has_include 预处理表达式
能够判断是否有某个头文件,代码可能会在不同编译器下工作,不同编译器的可用头文件有可能不同,所以能够应用此来判断:
#if defined __has_include
#if __has_include(<charconv>)
#define has_charconv 1
#include <charconv>
#endif
#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 指针,指向的是对象的援用,失常状况下可能没问题,然而如果多线程状况下,函数的作用域超过了对象的作用域,对象曾经被析构了,还拜访了成员变量,就会有问题。
struct A {
int a;
void func() {auto f = [this] {cout << a << endl;};
f();}
};
int main() {
A a;
a.func();
return 0;
}
所以 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;
}
新增 Attribute
咱们可能平时在我的项目中见过__declspec, attribute , #pragma 批示符,应用它们来给编译器提供一些额定的信息,来产生一些优化或特定的代码,也能够给其它开发者一些提示信息。
例如:
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,让编译期疏忽正告
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 函数,间接看代码:
#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 等,看代码:
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 的第一个类型个别要有对应的构造函数,否则编译失败:
struct A {A(int i){}};
int main() {std::variant<A, int> var; // 编译失败}
如何防止这种状况呢,能够应用 std::monostate 来打个桩,模仿一个空状态。
std::variant<std::monostate, A> var; // 能够编译胜利
std::optional
咱们有时候可能会有需要,让函数返回一个对象,如下:
struct A {};
A func() {if (flag) return A();
else {// 异常情况下,怎么返回异样值呢,想返回个空呢}
}
有一种方法是返回对象指针,异常情况下就能够返回 nullptr 啦,然而这就波及到了内存治理,兴许你会应用智能指针,但这里其实有更不便的方法就是 std::optional。
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 能够存储任何类型的单个值,见代码:
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 开展作为函数的参数传入,见代码:
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 开展作为结构函数参数
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 即可。
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 类型
std::string str = "str";
const std::string& constStr = std::as_const(str);
file_system
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);
std::shared_mutex
C++17 引入了 shared_mutex,能够实现读写锁,具体能够见我上一篇文章:
对于 C ++17 的介绍就到这里,心愿对大家有所帮忙~
参考资料
https://en.cppreference.com/w…
https://en.cppreference.com/w…
https://en.cppreference.com/w…
https://cloud.tencent.com/dev…
https://www.jianshu.com/p/9b8…
更多文章,请关注我的 V X 公 主 号:程序喵小孩儿,欢送交换。