小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

C 17 中那些值得關注的特性

 周磊0 2017-08-24


來源:祁宇(首發(fā)《程序員》雜志)  

www.iteye.com/news/32574

如有好的文章投稿,請點擊 → 這里查看詳情


引用


C++17標準在2017上半年已經討論確定,正在形成ISO標準文檔,今年晚些時候會正式發(fā)布。本文將介紹最新標準中值得開發(fā)者關注的新特新和基本用法。


總的來說C++17相比C++11的新特性來說新特性不算多,做了一些小幅改進。C++17增加了數十項新特性,值得關注的特性大概有下面這些:


  • constexpr if

  • constexpr lambda

  • fold expression

  • void_t

  • structured binding

  • std::apply, std::invoke

  • string_view

  • parallel STL

  • inline variable


剩下的有一些來自于boost庫,比如variant,any、optional和filesystem等特性,string_view其實在boost里也有。還有一些是語法糖,比如if init、deduction guide、guaranteed copy Elision、template、nested namespace、single param static_assert等特性。我接下來會介紹C++17主要的一些特性,介紹它們的基本用法和作用,讓讀者對C++17的新特性有一個基本的了解。


fold expression


C++11增加了一個新特性可變模版參數(variadic template),它可以接受任意個模版參數在參數包中,參數包是三個點…,它不能直接展開,需要通過一些特殊的方法才能展開,導致在使用的時候有點難度?,F在C++17解決了這個問題,讓參數包的展開變得容易了,Fold expression就是方便展開參數包的。


fold expression的語義


fold expression有4種語義:


  • unary right fold (pack op …)

  • unary left fold (… op pack)

  • binary right fold (pack op … op init)

  • binary left fold (init op … op pack)


其中pack代表變參,比如args,op代表操作符,fold expression支持32種操作符:


引用


+ - * / % ^ & | = > += -= *= /= %= ^= &= |= >= == != = && || , .* ->*


unary right fold的含義


fold (E op …) 意味著 E1 op (… op (EN-1 op EN)).


顧名思義,從右邊開始fold,看它是left fold還是right fold我們可以根據參數包…所在的位置來判斷,當參數包…在操作符右邊的時候就是right fold,在左邊的時候就是left fold。我們來看一個具體的例子:


template<typename... Args>

auto add_val(Args&&... args) {

    return (args +  ...);

}

 

auto t = add_val(1,2,3,4); //10


right fold的過程是這樣的:(1+(2+(3+4))),從右邊開始fold。


unary left fold的含義


fold (… op E) 意味著 ((E1 op E2) op …) op EN。


對于+這種滿足交換律的操作符來說left fold和right fold是一樣的,比如上面的例子你也可以寫成left fold。


template<typename... Args>

auto add_val(Args&&... args) {

    return (... + args);

}

 

auto t = add_val(1,2,3,4); //10


對于不滿足交換律的操作符來說就要注意了,比如減法。


template<typename... Args>

auto sub_val_right(Args&&... args) {

    return (args - ...);

}

 

 

template<typename... Args>

auto sub_val_left(Args&&... args) {

    return (... - args);

}

 

auto t = sub_val_right(2,3,4); //(2-(3-4)) = 3

auto t1 = sub_val_left(2,3,4); //((2-3)-4) = -5


這次right fold和left fold的結果就不一樣。


binary fold的含義


Binary right fold (E op … op I) 意味著 E1 op (… op (EN-1 op (EN op I)))。


Binary left fold (I op … op E) 意味著 (((I op E1) op E2) op …) op E2。


其中E代表變參,比如args,op代表操作符,I代表一個初始變量。


二元fold的語義和一元fold的語義是相同的,看一個二元操作符的例子:


template<typename... Args>

auto sub_one_left(Args&&... args) {

    return (1 - ... - args);

}

 

template<typename... Args>

auto sub_one_right(Args&&... args) {

    return (args - ... - 1);

}

 

auto t = sub_one_left(2,34);// (((1-2)-3)-4) = -8

auto t1 = sub_one_right(2,3,4);//(2-(3-(4-1))) = 2


相信通過這個例子大家應該對C++17的fold expression有了基本的了解。


comma fold


在C++17之前,我們經常使用逗號表達式和std::initializer_list來將變參一個個傳入一個函數。比如像下面這個例子:


template<typename T>

void print_arg(T t)

{

    std::cout << t << std::endl;

}

 

template<typename... Args>

void print2(Args... args)

{

    //int a[] = { (printarg(args), 0)... };

    std::initializer_list<int>{(print_arg(args), 0)...};

}


這種寫法比較繁瑣,用fold expression就會變得很簡單了。


template<typename... Args>

void print3(Args... args)

{

    (print_arg(args), ...);

}


這是right fold,你也可以寫成left fold,對于comma來說兩種寫法是一樣的,參數都是從左至右傳入print_arg函數。


template<typename... Args>

void print3(Args... args)

{

    (..., print_arg(args));

}


你也可以通過binary fold這樣寫:


template<typename ...Args>

void printer(Args&&... args) {

    (std::cout << ... << args) << '\n';

}


也許你會覺得能寫成這樣:


template<typename ...Args>

void printer(Args&&... args) {

    (std::cout << args << ...) << '\n';

}


但這樣寫是不合法的,根據binary fold的語法,參數包…必須在操作符中間,因此上面的這種寫法不符合語法要求。


借助comma fold我們可以簡化代碼,假如我們希望實現tuple的for_each算法,像這樣:


for_each(std::make_tuple(2.5, 10, 'a'),[](auto e) { std::cout << e<< '\n'; });


這個for_each將會遍歷tuple的元素并打印出來。在C++17之前我們如果要實現這個算法的話,需要借助逗號表達式和std::initializer_list來實現,類似于這樣:


template <typename... Args, typename Func, std::size_t... Idx>

void for_each(const std::tuple& t, Func&& f, std::index_sequence<Idx...>) {

    (void)std::initializer_list<int> { (f(std::get<Idx>(t)), void(), 0)...};

}


這樣寫比較繁瑣不直觀,現在借助fold expression我們可以簡化代碼了。


template <typename... Args, typename Func, std::size_t... Idx>

void for_each(const std::tuple<Args...>& t, Func&& f, std::index_sequence<Idx...>) {

    (f(std::get<Idx>(t)), ...);

}


借助coma fold我們可以寫很簡潔的代碼了。


constexpr if


constexpr標記一個表達式或一個函數的返回結果是編譯期常量,它保證函數會在編譯期執(zhí)行。相比模版來說,實現編譯期循環(huán)或遞歸,C++17中的constexpr if會讓代碼變得更簡潔易懂。比如實現一個編譯期整數加法:


template<int N>

constexpr int sum()

{

    return N;

}

 

template <int N, int N2, int... Ns>

constexpr int sum()

{

    return N + sum<N2, Ns...>();

}


C++17之前你可能需要像上面這樣寫,但是現在你可以寫更簡潔的代碼了。


template <int N, int... Ns>

constexpr auto sum17()

{

    if constexpr (sizeof...(Ns) == 0)

        return N;

    else

        return N + sum17<Ns...>();

}


當然,你也可以用C++17的fold expression:


template<typename ...Args>

constexpr int sum(Args... args) {

    return (0 + ... + args);

}


constexpr還可以用來消除enable_if了,對于討厭寫一長串enable_if的人來說會非常開心。比如我需要根據類型來選擇函數的時候:


template<typename T>

std::enable_if_t<std::is_integral<T>::value, std::string> to_str(T t)

{

    return std::to_string(t);

}

 

template<typename T>

std::enable_if_tstd::is_integral<T>::value, std::string> to_str(T t)

{

    return t;

}


經常不得不分開幾個函數來寫,還需要寫長長的enable_if,比較繁瑣,通過if constexpr可以消除enable_if了。


template<typename T>

auto to_str17(T t)

{

    if constexpr(std::is_integral<T>::value)

        return std::to_string(t);

    else

        return t;

}


constexpr if讓C++的模版具備if-else if-else功能了,是不是很酷,C++程序員的好日子來了。


不過需要注意的是下面這種寫法是有問題的。


template<typename T>

auto to_str17(T t)

{

    if constexpr(std::is_integral<T>::value)

        return std::to_string(t);

 

        return t;

}


這個代碼把else去掉了,當輸入如果是非數字類型時代碼可以編譯過,以為if constexpr在模版實例化的時候會丟棄不滿足條件的部分,因此函數體中的前兩行代碼將失效,只有最后一句有效。當輸入的為數字的時候就會產生編譯錯誤了,因為if constexpr滿足條件了,這時候就會有兩個return了,就會導致編譯錯誤。


constexpr if還可以用來替換#ifdef宏,看下面的例子:


enum class OS { Linux, Mac, Windows };

 

//Translate the macros to C++ at a single point in the application

#ifdef __linux__

constexpr OS the_os = OS::Linux;

#elif __APPLE__

constexpr OS the_os = OS::Mac;

#elif __WIN32

constexpr OS the_os = OS::Windows;

#endif

 

void do_something() {

     //do something general

 

     if constexpr (the_os == OS::Linux) {

         //do something Linuxy

     }

     else if constexpr (the_os == OS::Mac) {

         //do something Appley

     }

     else if constexpr (the_os == OS::Windows) {

         //do something Windowsy

     }

 

     //do something general

}


代碼變得更清爽了,再也不需要像以前一樣寫#ifdef那樣難看的代碼塊了。


constexpr lambda


constexpr lambda其實很簡單,它的意思就是可以在constexpr 函數中用lambda表達式了,這在C++17之前是不允許的。這樣使用constexpr函數和普通函數沒多大區(qū)別了,使用起來非常舒服。下面是constexpr lambda的例子:


template <typename I>

constexpr auto func(I i) {

  //use a lambda in constexpr context

  return [i](auto j){ return i + j; };

}


constexpr if和constexpr lambda是C++17提供的非常棒的特性,enjoy it.


string_view


string_view的基本用法


C++17中的string_view是一個char數據的視圖或者說引用,它并不擁有該數據,是為了避免拷貝,因此使用string_view可以用來做性能優(yōu)化。你應該用string_view來代替const char和const string了。string_view的方法和string類似,用法很簡單:


const char* data = 'test';

std::string_view str1(data, 4);

std::cout<<str1.length()<<'\n'; //4

if(data==str1)

    std::cout<<'ok'<<'\n';

 

const std::string str2 = 'test';

std::string_view str3(str2, str2.size());


構造string_view的時候用char*和長度來構造,這個長度可以自由確定,它表示string_view希望引用的字符串的長度。因為它只是引用其他字符串,所以它不會分配內存,不會像string那樣容易產生臨時變量。我們通過一個測試程序來看看string_view如何來幫我們優(yōu)化性能的。


using namespace std::literals;

 

constexpr auto s = 'it is a test'sv;

auto str = 'it is a test's;

 

constexpr int LEN = 1000000;

boost::timer t;

for (int i = 0; i < LEN; ++i) {

    constexpr auto s1 = s.substr(3);

}

std::cout<<t.elapsed()<<'\n';

t.restart();

for (int i = 0; i < LEN; ++i) {

    auto s2 = str.substr(3);

}

std::cout<<t.elapsed()<<'\n';

 

//output

0.004197

0.231505


我們可以通過字面量””sv來初始化string_view。string_view的substr和string的substr相比,快了50多倍,根本原因是它不會分配內存。


string_view的生命周期


由于string_vew并不擁有鎖引用的字符串,所以它也不會去關注被引用字符串的生命周期,用戶在使用的時候需要注意,不要將一個臨時變量給一個string_view,那樣會導致string_view引用的內容也失效。


std::string_view str_v;

{

    std::string temp = 'test';

    str_v = {temp};

}


這樣的代碼是有問題的,因為出了作用域之后,string_view引用的內容已經失效了。


總結


本文介紹了C++17的fold expression、constexpr if、constexpr lambda和string_view。fold expression為了簡化可變模板參數的展開,讓可以模板參數的使用變得更簡單直觀;constexpr if讓模板具備if-else功能,非常強大。它也避免了寫冗長的enable_if代碼,讓代碼變得簡潔易懂了;string_view則是用來做性能優(yōu)化的,應該用它來代替const char*和const string。 這些特性對之前的C++14和C++11做了改進和增強,非??帷?/p>


作者:祁宇,《深入應用C++11》作者,C++開源社區(qū)purecpp.org創(chuàng)始人,致力于C++11/14的應用、研究和推廣。樂于研究和分享技術,愛好C++,愛好開源。

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯系方式、誘導購買等信息,謹防詐騙。如發(fā)現有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多