C++20 要來了!

本文經受權轉自公衆號CSDN(ID:CSDNnews)ios


C++的新標準又雙叒叕要到來了,是的,C++20要來了!
c++

圖片來源:udemy.com程序員

幾周前,C++標準委會歷史上規模最大的一次會議(180人蔘會)在美國San Diego召開,此次的會議上討論肯定哪些特性要加入到C++20中,哪些特性可能加入到C++20中。在明年二月份的會議當中將正式肯定全部的C++20特性。算法

此次會議討論的提案也是很是之多,達到了創紀錄的274份,C++20的新特性若是要一一列出的話將是一份長長的清單,所以本文將只評論大部分肯定要加入和可能加入到C++20的重要特性,讓讀者對C++的將來和演進趨勢有一個基本的瞭解。編程

C++20中可能增長哪些重要特性,下面這個圖能夠提供一個參考。安全

下面是本文將評論的將進入和可能進入C++20的重要特性:微信

  • Concepts網絡

  • Rangesapp

  • Modules異步

  • Coroutines

  • Reflection

接下來讓咱們慢慢揭開C++20的面紗,看看這些特性究竟是什麼樣的,它們解決了什麼問題。


Concepts


在談Concepts以前我想先介紹一下Concepts提出的背景和緣由。衆所周知,由於C++的模版和模版元具有很是強大的泛型抽象能力而且是zero overhead,因此模版在C++中備受推崇,大獲成功,在各類C++庫(如STL)中被普遍使用。

然而,模版編程還存在一些問題,好比有些模版的代碼寫起來比較困難,讀起來比較難懂,尤爲是編譯出錯的時候,那些糟糕的讓人摸不着頭腦的錯誤提示讓人頭疼。所以,C++之父Bjarne Stroustrup很早就但願對模版作一些改進,讓C++的模版編程變得簡單好寫,錯誤提示更明確。他早在1987年就開始作這方面的嘗試了。

C++之父Bjarne Stroustrup

具體思路就是給模版參數加一些約束,這些約束相比以前的寫法具備更強的表達能力和可讀性,會簡化C++的泛型模版代碼的編寫。

因此Concepts的出現主要是爲了簡化泛型編程,一個Concept就是一個編譯期判斷,用於約束模版參數,Concepts則是這些編譯期判斷的合集。下面經過一個例子來展現Concepts是如何簡化模版編程的。

    template<typename T>
    class B {
    public:
      template<typename ToString = T>
      typename std::enable_if_t<std::is_convertible<ToString, std::string>::value, std::string>
      to_string() const {
        return "Class B<>";
      }
    };

    B<size_t> b1;                             // OK
    std::cout << b1.to_string() << std::endl// Compile ERROR!

    B<std::string> b2;                        // OK
    std::cout << b2.to_string() << std::endl// OK!

好比有這樣一個類B,咱們調用它的成員函數tostring時,對T類型進行限定,即限定T類型是std::string的可轉換類型,這樣作的目的是爲了更安全,能在編譯期就能檢查錯誤。這裏經過C++14的std::enableif_t來對T進行限定,可是長長的enableift看起來比較冗長繁瑣,頭重腳輕。來看看用Concepts怎麼寫這個代碼的。

    template<typename T>
    concept CastableToString = requires(T a) {
      { a } -> std::string;
    };

    template<typename T>
    class D {
    public:
      std::string to_string() const requires CastableToString<T> {
        return "Class D<>";
      }
    };

能夠看到,requires CastableToString比以前長長的enableift要簡潔很多,代碼可讀性也更好,CastableToString就是一個Concept,一個限定T爲能被轉換爲std::string類型的Concept,經過requires相鏈接,語義上也更明確了,並且這個Concept還能夠複用。

Concepts的這個語法也可能在最終的C++20中有少量不一樣,有可能還會變得更簡潔,如今語法有幾個候選版本,還沒最終投票肯定。


Ranges


相比STL,Ranges是更高一層的抽象,Ranges對STL作了改進,它是STL的下一代。爲何說Ranges是STL的將來?雖然STL在C++中提供的容器和算法備受推崇和普遍被使用,但STL一直存在兩個問題:

  • STL強制你必須傳一個begin和end迭代器用來遍歷一個容器;

  • STL算法不方便組合在一塊兒。

STL必須傳迭代器,這個迭代器僅僅是輔助你完成遍歷序列的技術細節,和咱們的函數功能無關,大部分時候咱們須要的是一個range,表明的是一個比迭代器更高層的抽象。

那麼Ranges究竟是什麼呢?Ranges是一個引用元素序列的對象,在概念上相似於一對迭代器。這意味着全部的STL容器都是Ranges。在Ranges裏咱們再也不傳迭代器了,而是傳range。好比下面的代碼:

STL寫法:

    std::vector<int> v{12};
    std::sort(v.begin(), v.end());

Ranges寫法:

    std::sort(v);

STL有時候不方便將一些算法組合在一塊兒,來看一個例子:

    std::vector<int> v{12345};

    std::vector<int> event_numbers;
    std::copy_if(v.begin(), v.end(), std::back_inserter(event_numbers), [](int i){ return i % 2 == 0;});

    std::vector<int> results;
    std::transform(event_numbers.begin(), event_numbers.end(), std::back_inserter(event_numbers), [](int i){ return i * 2;});

    for(int n : results){
        std::cout<<n<<' ';
    }
    //最終會輸出 4 8

上面這個例子但願獲得vector中的偶數乘以2的結果,需求很簡單,可是用STL寫起來仍是有些冗長繁瑣,中間還定義了兩個臨時變量。若是用Ranges來實現這個需求,代碼就會簡單得多。

    auto results = v | ranges::view::filter([](int i){ return i % 2 == 0; })
                     | ranges::view::transform([](int i){ return i * 2; });

用Concetps咱們能夠很方便地將算法組合在一塊兒,寫法更簡單,語義更清晰,而且還能夠實現延遲計算避免了中間的臨時變量,性能也會更好。

Concepts從設計上改進了以前STL的兩個問題,讓咱們的容器和算法變得更加簡單好用,還容易組合。


Modules


一直以來C++一直經過引用頭文件方式使用庫,而其餘90年代之後的語言好比Java、C#、Go等語言都是經過import包的方式來使用庫。如今C++決定改變這種狀況了,在C++20中將引入Modules,它和Java、Go等語言的包的概念是相似的,直接經過import包來使用庫,再也看不到頭文件了。

爲何C++20再也不但願使用#include方式了?由於使用頭文件方式存在很多問題,好比有include不少模版的頭文件將大大增長編譯時間,代碼生成物也會變大。並且引用頭文件方式不利於作一些C++庫和組件的管理工具,尤爲是對於一些雲環境和分佈式環境下不方便管理,C++一直缺一個包管理工具,這也是C++被吐槽得不少的地方,如今C++20 Modules將改變這一切。

Modules在程序中的結構以下圖:

上面的圖中,每一個方框表示一個翻譯單元,存放在一個文件裏而且能夠被獨立編譯。每一個Module由Module接口和實現組成,接口只有一份,實現能夠有多份。

Modules接口和實現的語法:

    export module module_name;

    module module_name;

使用Modules:

    import module_name;

Modules容許你導出類,函數,變量,常量和模版等等。

接下來看一個使用Modules的例子:

    import std.vector// #include <vector>
    import std.string// #include <string>
    import std.iostream; // #include <iostream>
    import std.iterator; // #include <iterator >
    int main() {
        using namespace std;
        vector<string> v = {
            "Socrates""Plato""Descartes""Kant""Bacon"
        };
        copy(begin(v), end(v), ostream_iterator<string>(cout"\n"));
    }

能夠看到不用再include了,直接去import須要用到的Modules便可,是否是有種似曾相識的感受呢。曾看到一我的說若是C++支持了Modules他就會從Java迴歸到C++,也說明這個特性也是很是受關注和期待的。


Coroutines


不少語言提供了Coroutine機制,由於Coroutine能夠大大簡化異步網絡程序的編寫,如今C++20中也要加入協程了(樂觀估計C++20加入,悲觀估計在C++23中加入)。

若是不用協程,寫一個異步的網絡程序是不那麼容易的,以boost.asio的異步網絡編程爲例,咱們須要注意的地方不少,好比異步事件完成的回調函數中須要保證調用對象仍然存在,如何構建異步回調鏈條等等,代碼比較複雜,並且出了問題也不容易調試。而協程給咱們提供了對異步編程優雅而高效的抽象,讓異步編程變得簡單!

C++ Courotines中增長了三個新的關鍵字:co_await,co_yield和co_return,若是一個函數體中有這三個關鍵字之一就變成Coroutine了。

co_await用來掛起和恢復一個協程,co_return用來返回協程的結果,co_yield返回一個值而且掛起協程。

下面來看看如何使用它們。

寫一個lazy sequence:

    generator<int> get_integers( int start=0int step=1 ) {
      for (int current=start; current+= step)
        co_yield current;
    }

    for(auto n : get_integers(05)){
      std::cout<<n<<" ";
    }
    std::cout<<'\n';

上面的例子每次調用get_integers,只返回一個整數,而後協程掛起,下次調用再返回一個整數,所以這個序列不是即時生成的,而是延遲生成的。

接下來再看一下co_wait是如何簡化異步網絡程序的編寫的:

    char data[1024];
    for (;;)
    {
      std::size_t n = co_await socket.async_read_some(boost::asio::buffer(data), token);
      co_await async_write(socket, boost::asio::buffer(data, n), token);
    }

這個例子僅僅用了四行代碼就完成了異步的echo,很是簡潔!co_await會在異步讀完成以前掛起協程,在異步完成以後恢復協程繼續執行,執行到async_write時又會掛起協程直到異步寫完成,異步寫完成以後繼續異步讀,如此循環。若是不用協程代碼會比較繁瑣,須要像這樣寫:

    void do_read()
    {
      auto self(shared_from_this());
      socket_.async_read_some(boost::asio::buffer(data_, max_length),
        [this, self](boost::system::error_code ec, std::size_t length)
         {
           if (!ec)
           {
             do_write(length);
           }
         });
      }

    void do_write(std::size_t length)
    {
      auto self(shared_from_this());
      boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
        [this, self](boost::system::error_code ec, std::size_t /*length*/)
        {
          if (!ec)
          {
            do_read();
          }
        });
    }

能夠看到,不使用協程來寫異步代碼的話,須要構建異步的回調鏈,須要保持異步回調的安全性等等。而使用協程能夠大大簡化異步網絡程序的編寫。


Reflection


C++中一直缺乏反射功能,其餘不少語言如Java、C#都具有運行期反射功能。反射能夠用來作不少事情:好比作對象的序列化,把對象序列化爲JSON、XML等格式,以及ORM中的實體映射,還有RPC遠程過程(方法)調用等,反射是應用程序中很是須要的基礎功能。如今C++終於要提供反射功能了,C++20中可會將反射做爲實驗庫,在C++23中正式加入到標準中。

在反射尚未進入到C++標準以前,有不少人作了一些編譯期反射的庫,好比purecpp社區開源的序列化引擎iguana,以及ORM庫ormpp,都是基於編譯期反射實現的。而後,非語言層面支持的反射庫存在種種不足之處,好比在實現上須要大量使用模版元和宏、不能訪問私有成員等問題。

如今C++終於要提供完備地編譯期反射功能了,爲何是編譯期反射而不是像其它語言同樣提供運行期反射,由於C++的一個重要設計哲學就是zero-overhead,編譯期反射效率遠高於運行期反射。

那麼,經過C++20的編譯期反射咱們能獲得什麼呢?咱們能夠獲得不少不少關於類型和對象的元信息,主要有:

  • 獲取對象類型或枚舉類型的成員變量,成員函數的類型;

  • 獲取類型和成員的名稱;

  • 獲取成員變量是靜態的仍是constexpr;

  • 獲取方法是virtual、public、protect仍是private;

  • 獲取類型定義時的源代碼所在的行和列。

因此C++20的反射實際上是提供了一些能夠編譯期向編譯器查詢目標類型「元數據」的API,下面來看看C++20的反射用法:

    struct person{
        int id;
        std::string name;
    };

    using MetaPerson = reflexpr(person);
    using Members = std::reflect::get_data_members_t<MetaPerson>;

    using Metax = std::reflect::get_data_members_t<Members>;
    constexpr bool is_public = std::reflect::is_public_v<Metax>;

    using Field0 = std::reflect::get_reflected_type_t<Metax>;// int

上面的例子中,C++20新增關鍵字reflexpr返回的是person的元數據類型,接下來咱們就能夠查詢這個元數據類型了,std::reflect::getdatamembers_t返回的是對象成員的元數據序列,咱們能夠像訪問tuple同樣訪問這個序列,獲得某一個字段的元數據以後咱們就能夠獲取它的具體信息了,好比它的具體類型是什麼,它的字段名是什麼,它是公有仍是私有的等等。

注意:C++20的反射語法尚未最終肯定,這只是一種候選的語法實現,還有一種沒有元編程的語法版本,該版本經過編譯期容器和字符串來存放元數據,好比constexpr std::vector,constexpr std::map,constexpr std::string等 ,這樣就能夠像普通的C++程序那樣來操做元數據了,用起來可能更簡單。

C++20的編譯期反射實際上提供了一些編譯期查詢AST信息的接口,功能完備而強大。


總結


  • Concepts讓C++的模版程序的編寫變得更簡單和容易理解;

  • Ranges讓咱們使用STL容器和算法更加簡單,而且更容易組合算法及延遲計算;

  • Modules幫助咱們大大加快編譯速度,同時彌補了C++使用庫和缺少包管理的缺陷;

  • Coroutines幫助咱們簡化異步程序的編寫;

  • Reflection給咱們提供強大的編譯期AST元數據查詢能力;

  • ......

關於C++20的更多細節讀者能夠在這裏查看:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/。

總而言之,C++的新標準都是爲了讓C++變得更簡單、更完善、更強大、更易學和使用,這也是C++之父但願將來C++演進的一個方向和目標。

C++20,一言以蔽之:Newer is Better!

在此呼籲如今仍然還在使用着20年前的標準C++98的公司儘早升級到最新的標準,跟上時代的發展,新標準意味這生產力和質量的提高,越早使用越早享受其帶來的好處!

做者簡介:祁宇,modern c++開源社區purecpp.org創始人,《深刻應用C++11》做者,開源庫cinatra、feather做者,熱愛開源,熱愛modern C++。樂於研究和分享技術,屢次在國際C++大會(cppcon)作演講。

致謝:感謝purecpp社區的朋友:袁秩昊,吳詠煒和張軼對本文部份內容的review。


本文經受權轉自公衆號CSDN(ID:CSDNnews)


—————END—————




喜歡本文的朋友們,歡迎長按下圖關注訂閱號程序員小灰,收看更多精彩內容



歡迎掃碼關注公衆號 小灰學英語,你學到的將不止是英語!


本文分享自微信公衆號 - 程序員小灰(chengxuyuanxiaohui)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索