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

分享

C 17在業(yè)務(wù)代碼中最好用的十個(gè)特性

 喜歡站在山上 2022-05-20 發(fā)布于吉林

作者:jinshang,騰訊WXG后臺(tái)開(kāi)發(fā)工程師

自從步入現(xiàn)代C++時(shí)代開(kāi)始,C++語(yǔ)言標(biāo)準(zhǔn)形成了三年一個(gè)版本的慣例:C++11標(biāo)志著現(xiàn)代C++的開(kāi)端,C++14在11的基礎(chǔ)上查缺補(bǔ)漏,并未加入許多新特性,而C++17作為C++11后的第一個(gè)大版本,標(biāo)志著現(xiàn)代C++逐漸走向成熟。WXG編譯器升級(jí)到gcc7.5已有一段時(shí)間,筆者所在項(xiàng)目組也已經(jīng)將全部代碼升級(jí)到C++17。在使用了c++17一年多之后,筆者總結(jié)了C++17在業(yè)務(wù)代碼中最好用的十個(gè)特性。

注1:本文只包含wxg的gcc7.5支持的特性,Execution Policy, File System等暫不支持的特性不包含在內(nèi)。

注2:本文只包含應(yīng)用于業(yè)務(wù)邏輯的特性,F(xiàn)old Expression, Mathematical Special Functions等適用于元編程和科學(xué)計(jì)算的特性并不包含。

筆者將這些特性大體上分為三類:語(yǔ)法糖、性能提升和類型系統(tǒng)


語(yǔ)法糖

這里所說(shuō)的語(yǔ)法糖,并不是嚴(yán)格意義上編程語(yǔ)言級(jí)別的語(yǔ)法糖,還包括一些能讓代碼更簡(jiǎn)潔更具有可讀性的函數(shù)和庫(kù):

結(jié)構(gòu)化綁定

c++17最便利的語(yǔ)法糖當(dāng)屬結(jié)構(gòu)化綁定。結(jié)構(gòu)化綁定是指將array、tuple或struct的成員綁定到一組變量*上的語(yǔ)法,最常用的場(chǎng)景是在遍歷map/unordered_map時(shí)不用再聲明一個(gè)中間變量了:

// pre c++17for(const auto& kv: map){ const auto& key = kv.first; const auto& value = kv.second; // ...}// c++17for(const auto& [key, value]: map){ // ...}

嚴(yán)格來(lái)說(shuō),結(jié)構(gòu)化綁定的結(jié)果并不是變量,c++標(biāo)準(zhǔn)稱之為名字/別名,這也導(dǎo)致它們不允許被lambda捕獲,但是gcc并沒(méi)有遵循c++標(biāo)準(zhǔn),所以以下代碼在gcc可以編譯,clang則編譯不過(guò)

for(const auto& [key, value]: map){    [&key, &value]{        std::cout << key << ': ' << value << std::endl;    }();}

在clang環(huán)境下,可以在lambda表達(dá)式捕獲時(shí)顯式引入一個(gè)引用變量通過(guò)編譯

for(const auto& [key, value]: map){ [&key = key, &value = value]{ std::cout << key << ': ' << value << std::endl; }();}

另外這條限制在c++20中已經(jīng)被刪除,所以在c++20標(biāo)準(zhǔn)中g(shù)cc和clang都可以捕獲結(jié)構(gòu)化綁定的對(duì)象了。

std::tuple的隱式推導(dǎo)

在c++17以前,構(gòu)造std::pair/std::tuple時(shí)必須指定數(shù)據(jù)類型或使用std::make_pair/std::make_tuple函數(shù),c++17為std::pair/std::tuple新增了推導(dǎo)規(guī)則,可以不再顯示指定類型。

// pre c++17std::pair<int, std::string> p1{3.14, 'pi's};auto p1 = std::make_pair(3.14, 'pi's);// c++17std::pair p3{3.14, 'pi's};

if constexpr

if constexpr語(yǔ)句是編譯期的if判斷語(yǔ)句,在C++17以前做編譯期的條件判斷往往通過(guò)復(fù)雜SFINAE機(jī)制或模版重載實(shí)現(xiàn),甚至嫌麻煩的時(shí)候直接放到運(yùn)行時(shí)用if判斷,造成性能損耗,if constexpr大大緩解了這個(gè)問(wèn)題。比如我想實(shí)現(xiàn)一個(gè)函數(shù)將不同類型的輸入轉(zhuǎn)化為字符串,在c++17之前需要寫(xiě)三個(gè)函數(shù)去實(shí)現(xiàn),而c++17只需要一個(gè)函數(shù)。

// pre c++17template <typename T>std::string convert(T input){ return std::to_string(input);}// const char*和string進(jìn)行特殊處理std::string convert(const char* input){ return input;}std::string convert(std::string input){ return input;}
// c++17template <typename T>std::string convert(T input) {    if constexpr (std::is_same_v<T, const char*> ||                  std::is_same_v<T, std::string>) {        return input;    } else {        return std::to_string(input);    }}

if初始化語(yǔ)句

c++17支持在if的判斷語(yǔ)句之前增加一個(gè)初始化語(yǔ)句,將僅用于if語(yǔ)句內(nèi)部的變量聲明在if內(nèi),有助于提升代碼的可讀性。且對(duì)于lock/iterator等涉及并發(fā)/RAII的類型更容易保證程序的正確性。

// c++ 17std::map<int, std::string> m;std::mutex mx;extern bool shared_flag; // guarded by mx int demo(){ if (auto it = m.find(10); it != m.end()) { return it->second.size(); } if (char buf[10]; std::fgets(buf, 10, stdin)) { m[0] += buf; } if (std::lock_guard lock(mx); shared_flag) { unsafe_ping(); shared_flag = false; } if (int s; int count = ReadBytesWithSignal(&s)) { publish(count); raise(s); } if (const auto keywords = {'if', 'for', 'while'}; std::ranges::any_of(keywords, [&tok](const char* kw) { return tok == kw; })) { std::cerr << 'Token must not be a keyword\n'; }}

性能提升

std::shared_mutex

shared_mutex是c++的原生讀寫(xiě)鎖實(shí)現(xiàn),有共享和獨(dú)占兩種鎖模式,適用于并發(fā)高的讀場(chǎng)景下,通過(guò)reader之前共享鎖來(lái)提升性能。在c++17之前,只能自己通過(guò)獨(dú)占鎖和條件變量自己實(shí)現(xiàn)讀寫(xiě)鎖或使用c++14加入的性能較差的std::shared_timed_mutex。以下是通過(guò)shared_mutex實(shí)現(xiàn)的線程安全計(jì)數(shù)器:

// c++17class ThreadSafeCounter { public:  ThreadSafeCounter() = default;   // Multiple threads/readers can read the counter's value at the same time.  unsigned int get() const {    std::shared_lock lock(mutex_);    return value_;  }   // Only one thread/writer can increment/write the counter's value.  unsigned int increment() {    std::unique_lock lock(mutex_);    return ++value_;  }   // Only one thread/writer can reset/write the counter's value.  void reset() {    std::unique_lock lock(mutex_);    value_ = 0;  }  private:  mutable std::shared_mutex mutex_;  unsigned int value_ = 0;};

std::string_view

std::string_view顧名思義是字符串的“視圖”,類成員變量包含兩個(gè)部分:字符串指針和字符串長(zhǎng)度,std::string_view涵蓋了std::string的所有只讀接口。std::string_view對(duì)字符串不具有所有權(quán),且兼容std::string和const char*兩種類型。

c++17之前,我們處理只讀字符串往往使用const std::string&,std::string有兩點(diǎn)性能優(yōu)勢(shì):

  1. 兼容兩種字符串類型,減少類型轉(zhuǎn)換和內(nèi)存分配。如果傳入的是明文字符串const char*, const std::string&需要進(jìn)行一次內(nèi)存分配,將字符串拷貝到堆上,而std::string_view則可以避免。
  2. 在處理子串時(shí),std::string::substr也需要進(jìn)行拷貝和分配內(nèi)存,而std::string_view::substr則不需要,在處理大文件解析時(shí),性能優(yōu)勢(shì)非常明顯。
// from https:///a/40129046// author: Pavel Davydov// string_view的remove_prefix比const std::string&的快了15string remove_prefix(const string &str) { return str.substr(3);}string_view remove_prefix(string_view str) { str.remove_prefix(3); return str;}static void BM_remove_prefix_string(benchmark::State& state) { std::string example{'asfaghdfgsghasfasg3423rfgasdg'}; while (state.KeepRunning()) { auto res = remove_prefix(example); // auto res = remove_prefix(string_view(example)); for string_view if (res != 'aghdfgsghasfasg3423rfgasdg') { throw std::runtime_error('bad op'); } }}

std::map/unordered_map try_emplace

在向std::map/unordered_map中插入元素時(shí),我們往往使用emplace,emplace的操作是如果元素key不存在,則插入該元素,否則不插入。但是在元素已存在時(shí),emplace仍會(huì)構(gòu)造一次待插入的元素,在判斷不需要插入后,立即將該元素析構(gòu),因此進(jìn)行了一次多余構(gòu)造和析構(gòu)操作。c++17加入了try_emplace,避免了這個(gè)問(wèn)題。同時(shí)try_emplace在參數(shù)列表中將key和value分開(kāi),因此進(jìn)行原地構(gòu)造的語(yǔ)法比emplace更加簡(jiǎn)潔

std::map<std::string, std::string> m;// emplace的原地構(gòu)造需要使用std::piecewise_construct,因?yàn)槭侵苯硬迦雜td::pair<key, value>m.emplace(std::piecewise_construct,           std::forward_as_tuple('c'),           std::forward_as_tuple(10, 'c'));// try_emplace可以直接原地構(gòu)造,因?yàn)閰?shù)列表中key和value是分開(kāi)的m.try_emplace('c', 10, 'c')

同時(shí),c++17還給std::map/unordered_map加入了insert_or_assign函數(shù),可以更方便地實(shí)現(xiàn)插入或修改語(yǔ)義

類型系統(tǒng)

c++17進(jìn)一步完備了c++的類型系統(tǒng),終于加入了眾望所歸的類型擦除容器(Type Erasure)和代數(shù)數(shù)據(jù)類型(Algebraic Data Type)

std::any

std::any是一個(gè)可以存儲(chǔ)任何可拷貝類型的容器,C語(yǔ)言中通常使用void*實(shí)現(xiàn)類似的功能,與void*相比,std::any具有兩點(diǎn)優(yōu)勢(shì):

  1. std::any更安全:在類型T被轉(zhuǎn)換成void*時(shí),T的類型信息就已經(jīng)丟失了,在轉(zhuǎn)換回具體類型時(shí)程序無(wú)法判斷當(dāng)前的void*的類型是否真的是T,容易帶來(lái)安全隱患。而std::any會(huì)存儲(chǔ)類型信息,std::any_cast是一個(gè)安全的類型轉(zhuǎn)換。
  2. std::any管理了對(duì)象的生命周期,在std::any析構(gòu)時(shí),會(huì)將存儲(chǔ)的對(duì)象析構(gòu),而void*則需要手動(dòng)管理內(nèi)存。

std::any應(yīng)當(dāng)很少是程序員的第一選擇,在已知類型的情況下,std::optional, std::variant和繼承都是比它更高效、更合理的選擇。只有當(dāng)對(duì)類型完全未知的情況下,才應(yīng)當(dāng)使用std::any,比如動(dòng)態(tài)類型文本的解析或者業(yè)務(wù)邏輯的中間層信息傳遞。

std::optional

std::optional<T>代表一個(gè)可能存在的T值,對(duì)應(yīng)Haskell中的Maybe和Rust/OCaml中的option,實(shí)際上是一種Sum Type。常用于可能失敗的函數(shù)的返回值中,比如工廠函數(shù)。在C++17之前,往往使用T*作為返回值,如果為nullptr則代表函數(shù)失敗,否則T*指向了真正的返回值。但是這種寫(xiě)法模糊了所有權(quán),函數(shù)的調(diào)用方無(wú)法確定是否應(yīng)該接管T*的內(nèi)存管理,而且T*可能為空的假設(shè),如果忘記檢查則會(huì)有SegFault的風(fēng)險(xiǎn)。

// pre c++17ReturnType* func(const std::string& in) { ReturnType* ret = new ReturnType; if (in.size() == 0) return nullptr; // ... return ret;}// c++17 更安全和直觀std::optional<ReturnType> func(const string& in) { ReturnType ret; if (in.size() == 0) return nullopt; // ... return ret;}

std::variant

std::variant<T, U, ...>代表一個(gè)多類型的容器,容器中的值是制定類型的一種,是通用的Sum Type,對(duì)應(yīng)Rust的enum。是一種類型安全的union,所以也叫做tagged union。與union相比有兩點(diǎn)優(yōu)勢(shì):

  1. 可以存儲(chǔ)復(fù)雜類型,而union只能直接存儲(chǔ)基礎(chǔ)的POD類型,對(duì)于如std::vectorstd::string就等復(fù)雜類型則需要用戶手動(dòng)管理內(nèi)存。
  2. 類型安全,variant存儲(chǔ)了內(nèi)部的類型信息,所以可以進(jìn)行安全的類型轉(zhuǎn)換,c++17之前往往通過(guò)union+enum來(lái)實(shí)現(xiàn)相同功能。

通過(guò)使用std::variant<T, Err>,用戶可以實(shí)現(xiàn)類似Rust的std::result,即在函數(shù)執(zhí)行成功時(shí)返回結(jié)果,在失敗時(shí)返回錯(cuò)誤信息,上文的例子則可以改成:

std::variant<ReturnType, Err> func(const string& in) {    ReturnType ret;    if (in.size() == 0)        return Err{'input is empty'};    // ...    return {ret};}

需要注意的是,c++17只提供了一個(gè)庫(kù)級(jí)別的variant實(shí)現(xiàn),沒(méi)有對(duì)應(yīng)的模式匹配(Pattern Matching)機(jī)制,而最接近的std::visit又缺少編譯器的優(yōu)化支持,所以在c++17中std::variant并不好用,跟Rust和函數(shù)式語(yǔ)言中出神入化的Sum Type還相去甚遠(yuǎn),但是已經(jīng)有許多圍繞std::variant的提案被提交給c++委員會(huì)探討,包括模式匹配,std::expected等等。

總結(jié)一下,c++17新增的三種類型給c++帶來(lái)了更現(xiàn)代更安全的類型系統(tǒng),它們對(duì)應(yīng)的使用場(chǎng)景是:

  • std::any適用于之前使用void*作為通用類型的場(chǎng)景。
  • std::optional適用于之前使用nullptr代表失敗狀態(tài)的場(chǎng)景。
  • std::variant適用于之前使用union的場(chǎng)景。

總結(jié)

以上是筆者在生產(chǎn)環(huán)境中最常用的c++17特性,除了本文描述的十個(gè)特性外,c++17還添加了如lambda值捕獲*this, 鉗夾函數(shù)std::clamp(), 強(qiáng)制檢查返回值[[nodiscard]]等非常易用的特性,本文篇幅有限不做贅述,歡迎有興趣的讀者自行探索。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多