他來了,他來了,C++17新特性精華都在這了
本文轉(zhuǎn)載自微信公眾號(hào)「程序喵大人」,作者程序喵大人 。轉(zhuǎn)載本文請(qǐng)聯(lián)系程序喵大人公眾號(hào)。
程序喵之前已經(jīng)介紹過C++11的新特性和C++14的新特性(點(diǎn)擊對(duì)應(yīng)文字,直接訪問),今天向親愛的讀者們介紹下C++17的新特性,現(xiàn)在基本上各個(gè)編譯器對(duì)C++17都已經(jīng)提供完備的支持,建議大家編程中嘗試使用下C++17,可以一定程度上簡(jiǎn)化代碼編寫,提高編程效率。
主要新特性如下:
- 構(gòu)造函數(shù)模板推導(dǎo)
- 結(jié)構(gòu)化綁定
- if-switch語(yǔ)句初始化
- 內(nèi)聯(lián)變量
- 折疊表達(dá)式
- constexpr lambda表達(dá)式
- namespace嵌套
- __has_include預(yù)處理表達(dá)式
- 在lambda表達(dá)式用*this捕獲對(duì)象副本
- 新增Attribute
- 字符串轉(zhuǎn)換
- std::variant
- std::optional
- std::any
- std::apply
- std::make_from_tuple
- as_const
- std::string_view
- file_system
- std::shared_mutex
下面,程序喵一一介紹:
構(gòu)造函數(shù)模板推導(dǎo)
在C++17前構(gòu)造一個(gè)模板類對(duì)象需要指明類型:
- pair<int, double> p(1, 2.2); // before c++17
C++17就不需要特殊指定,直接可以推導(dǎo)出類型,代碼如下:
- pair p(1, 2.2); // c++17 自動(dòng)推導(dǎo)
- vector v = {1, 2, 3}; // c++17
結(jié)構(gòu)化綁定
通過結(jié)構(gòu)化綁定,對(duì)于tuple、map等類型,獲取相應(yīng)值會(huì)方便很多,看代碼:
- std::tuple<int, double> func() {
- return std::tuple(1, 2.2);
- }
- int main() {
- auto[i, d] = func(); //是C++11的tie嗎?更高級(jí)
- 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;
- }
結(jié)構(gòu)化綁定還可以改變對(duì)象的值,使用引用即可:
- // 進(jìn)化,可以通過結(jié)構(gòu)化綁定改變對(duì)象的值
- int main() {
- std::pair a(1, 2.3f);
- auto& [i, f] = a;
- i = 2;
- cout << a.first << endl; // 2
- }
注意結(jié)構(gòu)化綁定不能應(yīng)用于constexpr
- constexpr auto[x, y] = std::pair(1, 2.3f); // compile error, C++20可以
結(jié)構(gòu)化綁定不止可以綁定pair和tuple,還可以綁定數(shù)組和結(jié)構(gòu)體等。
- 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();
這里其實(shí)可以實(shí)現(xiàn)自定義類的結(jié)構(gòu)化綁定,代碼如下:
- // 需要實(shí)現(xiàn)相關(guān)的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語(yǔ)句初始化
C++17前if語(yǔ)句需要這樣寫代碼:
- 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;
- }
使用這種方式可以盡可能約束作用域,讓代碼更簡(jiǎn)潔,但是可讀性略有下降。
內(nèi)聯(lián)變量
C++17前只有內(nèi)聯(lián)函數(shù),現(xiàn)在有了內(nèi)聯(lián)變量,我們印象中C++類的靜態(tài)成員變量在頭文件中是不能初始化的,但是有了內(nèi)聯(lián)變量,就可以達(dá)到此目的:
- // header file
- struct A {
- static const int value;
- };
- inline int const A::value = 10;
- // ==========或者========
- struct A {
- inline static const int value = 10;
- }
折疊表達(dá)式
C++17引入了折疊表達(dá)式使可變參數(shù)模板編程更方便:
- 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表達(dá)式
C++17前l(fā)ambda表達(dá)式只能在運(yùn)行時(shí)使用,C++17引入了constexpr lambda表達(dá)式,可以用于在編譯期進(jìn)行計(jì)算。
- int main() { // c++17可編譯
- constexpr auto lamb = [] (int n) { return n * n; };
- static_assert(lamb(3) == 9, "a");
- }
注意
constexpr函數(shù)有如下限制:
函數(shù)體不能包含匯編語(yǔ)句、goto語(yǔ)句、label、try塊、靜態(tài)變量、線程局部存儲(chǔ)、沒有初始化的普通變量,不能動(dòng)態(tài)分配內(nèi)存,不能有new delete等,不能虛函數(shù)。
namespace嵌套
- namespace A {
- namespace B {
- namespace C {
- void func();
- }
- }
- }
- // c++17,更方便更舒適
- namespace A::B::C {
- void func();)
- }
__has_include預(yù)處理表達(dá)式
可以判斷是否有某個(gè)頭文件,代碼可能會(huì)在不同編譯器下工作,不同編譯器的可用頭文件有可能不同,所以可以使用此來判斷:
- #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...
- 其它方式實(shí)現(xiàn)
- #endif
- return std::nullopt;
- }
在lambda表達(dá)式用*this捕獲對(duì)象副本
正常情況下,lambda表達(dá)式中訪問類的對(duì)象成員變量需要捕獲this,但是這里捕獲的是this指針,指向的是對(duì)象的引用,正常情況下可能沒問題,但是如果多線程情況下,函數(shù)的作用域超過了對(duì)象的作用域,對(duì)象已經(jīng)被析構(gòu)了,還訪問了成員變量,就會(huì)有問題。
- 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指針,而是持有對(duì)象的拷貝,這樣生命周期就與對(duì)象的生命周期不相關(guān)啦。
- struct A {
- int a;
- void func() {
- auto f = [*this] { // 這里
- cout << a << endl;
- };
- f();
- }
- };
- int main() {
- A a;
- a.func();
- return 0;
- }
新增Attribute
我們可能平時(shí)在項(xiàng)目中見過__declspec__, __attribute__ , #pragma指示符,使用它們來給編譯器提供一些額外的信息,來產(chǎn)生一些優(yōu)化或特定的代碼,也可以給其它開發(fā)者一些提示信息。
例如:
- struct A { short f[3]; } __attribute__((aligned(8)));
- void fatal() __attribute__((noreturn));
在C++11和C++14中有更方便的方法:
- [[carries_dependency]] 讓編譯期跳過不必要的內(nèi)存柵欄指令
- [[noreturn]] 函數(shù)不會(huì)返回
- [[deprecated]] 函數(shù)將棄用的警告
- [[noreturn]] void terminate() noexcept;
- [[deprecated("use new func instead")]] void func() {}
C++17又新增了三個(gè):
[[fallthrough]]:用在switch中提示可以直接落下去,不需要break,讓編譯期忽略警告
- switch (i) {}
- case 1:
- xxx; // warning
- case 2:
- xxx;
- [[fallthrough]]; // 警告消除
- case 3:
- xxx;
- break;
- }
使得編譯器和其它開發(fā)者都可以理解開發(fā)者的意圖。
[[nodiscard]] :表示修飾的內(nèi)容不能被忽略,可用于修飾函數(shù),標(biāo)明返回值一定要被處理
- [[nodiscard]] int func();
- void F() {
- func(); // warning 沒有處理函數(shù)返回值
- }
[[maybe_unused]] :提示編譯器修飾的內(nèi)容可能暫時(shí)沒有使用,避免產(chǎn)生警告
- void func1() {}
- [[maybe_unused]] void func2() {} // 警告消除
- void func3() {
- int x = 1;
- [[maybe_unused]] int y = 2; // 警告消除
- }
字符串轉(zhuǎn)換
新增from_chars函數(shù)和to_chars函數(shù),直接看代碼:
- #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
- }
注意
一般情況下variant的第一個(gè)類型一般要有對(duì)應(yīng)的構(gòu)造函數(shù),否則編譯失敗:
- struct A {
- A(int i){}
- };
- int main() {
- std::variant<A, int> var; // 編譯失敗
- }
如何避免這種情況呢,可以使用std::monostate來打個(gè)樁,模擬一個(gè)空狀態(tài)。
- std::variant<std::monostate, A> var; // 可以編譯成功
std::optional
我們有時(shí)候可能會(huì)有需求,讓函數(shù)返回一個(gè)對(duì)象,如下:
- struct A {};
- A func() {
- if (flag) return A();
- else {
- // 異常情況下,怎么返回異常值呢,想返回個(gè)空呢
- }
- }
有一種辦法是返回對(duì)象指針,異常情況下就可以返回nullptr啦,但是這就涉及到了內(nèi)存管理,也許你會(huì)使用智能指針,但這里其實(shí)有更方便的辦法就是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可以存儲(chǔ)任何類型的單個(gè)值,見代碼:
- 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展開作為函數(shù)的參數(shù)傳入,見代碼:
- 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展開作為構(gòu)造函數(shù)參數(shù)
- 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
通常我們傳遞一個(gè)string時(shí)會(huì)觸發(fā)對(duì)象的拷貝操作,大字符串的拷貝賦值操作會(huì)觸發(fā)堆內(nèi)存分配,很影響運(yùn)行效率,有了string_view就可以避免拷貝操作,平時(shí)傳遞過程中傳遞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可以將左值轉(zhuǎn)成const類型
- std::string str = "str";
- const std::string& constStr = std::as_const(str);
file_system
C++17正式將file_system納入標(biāo)準(zhǔn)中,提供了關(guān)于文件的大多數(shù)功能,基本上應(yīng)有盡有,這里簡(jiǎn)單舉幾個(gè)例子:
- 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,可以實(shí)現(xiàn)讀寫鎖,具體可以見我上一篇文章:C++14新特性的所有知識(shí)點(diǎn)全在這兒啦!
關(guān)于C++17的介紹就到這里,希望對(duì)大家有所幫助~
參考資料
https://en.cppreference.com/w/cpp/utility/make_from_tuple
https://en.cppreference.com/w/cpp/utility/apply
https://en.cppreference.com/w/cpp/17
https://cloud.tencent.com/developer/article/1383177
https://www.jianshu.com/p/9b8eeddbf1e4