C++17新特性

程序喵以前已經介紹過C++11的新特性和C++14的新特性,連接以下:xxx,今天向親愛的讀者們介紹下C++17的新特性,如今基本上各個編譯器對C++17都已經提供完備的支持,建議你們編程中嘗試使用下C++17,能夠必定程度上簡化代碼編寫,提升編程效率。c++

主要新特性以下:編程

  • 構造函數模板推導
  • 結構化綁定
  • 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就不須要特殊指定,直接能夠推導出類型,代碼以下:app

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 
}

注意結構化綁定不能應用於constexprthis

constexpr auto[x, y] = std::pair(1, 2.3f); // compile error, C++20能夠

結構化綁定不止能夠綁定pair和tuple,還能夠綁定數組和結構體等spa

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 公 主 號:程序喵大人,歡迎交流。

相關文章
相關標籤/搜索