最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

解析C++中std::ref的使用

 更新時間:2023年08月07日 09:18:03   作者:leapmotion  
這篇文章主要介紹了解析C++中std::ref的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

1. 前言

關(guān)于c++中的std::ref,std::ref在c++11引入。

本文通過講解std::ref的常用方式,及剖析下std::ref內(nèi)部實現(xiàn),進而再來講解下std::reference_wrapper,然后我們再進一步分析為什么使用std::ref。

2. std::ref 用法

簡單舉例來說:

int n1 = 0;
auto n2 = std::ref(n1);
n2++;
n1++;
std::cout << n1 << std::endl;  // 2
std::cout << n2 << std::endl;  // 2

可以看到 是把n1的引用傳遞給了n2,分別進行加法,可以看到n2是n1的引用,最終得到的值都是2

那么大家可能會想,我都已經(jīng)有了’int& a = b’的這種引用賦值的語法了,為什么c++11又出現(xiàn)了一個std::ref,我們繼續(xù)來看例子:

#include <iostream>
#include <thread>
void thread_func(int& n2) { // error, >> int n2
    n2++;
}
int main() {
    int n1 = 0;
    std::thread t1(thread_func, n1);
    t1.join();
    std::cout << n1 << std::endl;
}

我們?nèi)绻麑懗蛇@樣是編譯不過的,除非是去掉引用符號,那么我如果非要傳引用怎么辦呢?

// snap ...
int main() {
    int n1 = 0;
    std::thread t1(thread_func, std::ref(n1));
    t1.join();
    std::cout << n1 << std::endl; // 1
}

這樣可以看到引用傳遞成功,并且能夠達到我們效果,我們再來看個例子:

#include <iostream>
#include <functional>
void func(int& n2) {
    n2++;
}
int main() {
    int n1 = 0;
    auto bind_fn = std::bind(&func, std::ref(n1));
    bind_fn();
    std::cout << n1 << std::endl; // 1
}

這里我們也發(fā)現(xiàn)std::bind這樣也是需要通過std::ref來實現(xiàn)bind引用。

那么我們其實可以看的出來,std::bind或者std::thread里是做了什么導(dǎo)致我們原來的通過&傳遞引用的方式失效,或者說std::ref是做了什么才能使得我們使用std::bind和std::thread能夠傳遞引用。

那么我們展開std::ref看看他的真面目,大致內(nèi)容如下:

template <class _Ty>
reference_wrapper<_Ty> ref(_Ty& _Val) noexcept {
    return reference_wrapper<_Ty>(_Val);
}

這里我們看到std::ref最終只是被包裝成reference_wrapper返回,所以關(guān)鍵點還是std::reference_wrapper

3. std::reference_wrapper

關(guān)于這個類,我們看下cppreference上的實現(xiàn)形式為:

namespace detail {
template <class T> constexpr T& FUN(T& t) noexcept { return t; }
template <class T> void FUN(T&&) = delete;
}
template <class T>
class reference_wrapper {
public:
  // types
  typedef T type;
  // construct/copy/destroy
  template <class U, class = decltype(
    detail::FUN<T>(std::declval<U>()),
    std::enable_if_t<!std::is_same_v<reference_wrapper, std::remove_cvref_t<U>>>()
  )>
  constexpr reference_wrapper(U&& u) noexcept(noexcept(detail::FUN<T>(std::forward<U>(u))))
    : _ptr(std::addressof(detail::FUN<T>(std::forward<U>(u)))) {}
  reference_wrapper(const reference_wrapper&) noexcept = default;
  // 賦值
  reference_wrapper& operator=(const reference_wrapper& x) noexcept = default;
  // 訪問
  constexpr operator T& () const noexcept { return *_ptr; }
  constexpr T& get() const noexcept { return *_ptr; }
  template< class... ArgTypes >
  constexpr std::invoke_result_t<T&, ArgTypes...>
    operator() ( ArgTypes&&... args ) const {
    return std::invoke(get(), std::forward<ArgTypes>(args)...);
  }
private:
  T* _ptr;
};
// deduction guides
template<class T>
reference_wrapper(T&) -> reference_wrapper<T>;

里邊有一些語法比較晦澀,我們一點一點的來看

最開始是一個detail的namespace,里邊有兩個函數(shù),第一個是接收左值引用的,第二個是接收右值引用的,接收右值引用的被delete,不能調(diào)用。這里detail是為后邊做校驗的,大家可能會像,不用右值引用不寫就可以了,為啥寫了這個函數(shù)還要標(biāo)記為delete。這是因為如果沒有第二個函數(shù)右值參數(shù)是可以傳遞給第一個函數(shù)的,如果寫了就會優(yōu)先匹配到到第二個函數(shù),發(fā)現(xiàn)這個函數(shù)是delete,不能編譯通過,明白了這個我們繼續(xù)。

接著我們看到reference_wrapper,首先是一個模板,看到很長的一個構(gòu)造函數(shù),我們拆開來看,template <class U, class = xxx>這種寫法,后邊那個class=也是在編譯期做校驗使用,SFINEA的一種實現(xiàn)形式吧,如果class=后邊那個編譯不過,那么你就不可以使用這個構(gòu)造函數(shù)。

class=后邊這段很長的代碼:

template <class U, class = decltype(
    detail::FUN<T>(std::declval<U>()),
    std::enable_if_t<!std::is_same_v<reference_wrapper, std::remove_cvref_t<U>>>()
  )>

首先是一個decltype關(guān)鍵字,得到的是一個類型。decltype內(nèi)部是使用逗號表達式連接兩部分,逗號左邊部分調(diào)用detail的FUN來校驗,std::declval是不用調(diào)用構(gòu)造函數(shù)便可以使用類的成員函數(shù),不過只能用于不求值語境。獲取U的對象看下是否是右值,上邊也說到如果右值則編譯不過。

如果是左值的話看逗號右邊的部分,std::enable_if_t<>, 這里<>中的第一個參數(shù)是條件,如果條件滿足返回第二個參數(shù),第二個參數(shù)是類型, 這里沒有第二個參數(shù),默認(rèn)是void,即如果滿足條件可以編譯通過,否則編譯不通過。條件是std::is_same_v取反,std::is_same_v<>是如果兩個模板參數(shù)相同類型則是true,否則false。

所以reference_wrapper和std::remove_cvref_t<U>不相同則可以通過編譯,std::remove_cvref_t這個模板又是去掉U這個類型的const,volatile和引用的屬性,單純兩個類型比較。

上邊總結(jié)就是在調(diào)用構(gòu)造函數(shù)時,首先進行校驗,傳入?yún)?shù)時右值和reference_wrapper類型就不能編譯通過。

然后是構(gòu)造函數(shù)的正文:

constexpr reference_wrapper(U&& u) noexcept(noexcept(detail::FUN<T>(std::forward<U>(u))))
    : _ptr(std::addressof(detail::FUN<T>(std::forward<U>(u)))) {}

這里先看下“noexcept(noexcept(detail::FUN(std::forward<U>(u))))”這段代碼,不了解noexcept我這里大概講解下。

  • 語法上來說noexcept分為修飾符和操作符兩種分類吧。
  • 修飾符寫法是noexcept(expression),expression是常量表達式,expression這個值返回true則編譯器認(rèn)為修飾的函數(shù)不拋出異常,這時如果該函數(shù)再拋出異常則調(diào)用std::abort終止程序,如果值返回false則認(rèn)為該函數(shù)可能會拋出異常。而我們常看到函數(shù)聲明后邊只寫一個noexcept,其實也是相當(dāng)于noexcept(true)。
  • 操作符大都用于模板中,寫法就是我們這里縮寫的那樣noexcept(noexcept(T())),那么這里T()決定該函數(shù)是否拋出異常,如果T()會拋出異常那么第二個noexcept就會返回false,否則返回true。

那么這里構(gòu)造函數(shù)就是說如果執(zhí)行“detail::FUN(std::forward<U>(u))”不會拋出異常,那么就不會拋出異常,這樣也是更好的告知編譯器一個條件吧。

繼續(xù)的就是_ptr存放的是傳進來參數(shù)的地址,這里也是比較關(guān)鍵,相當(dāng)于是reference_wrapper的實現(xiàn)就是通過保存?zhèn)鬟M來參數(shù)的地址來達到引用的包裝(ref wrapper)效果。

構(gòu)造函數(shù)終于講完了,拷貝構(gòu)造函數(shù)和賦值運算符應(yīng)該不用講了

再然后就是看下如何訪問了

constexpr operator T& () const noexcept { return *_ptr; }
constexpr T& get() const noexcept { return *_ptr; }

這兩個也比較簡單,提供了一個get函數(shù)和()的重載,實現(xiàn)就是獲取_ptr存放地址所指向的值。

template< class... ArgTypes >
  constexpr std::invoke_result_t<T&, ArgTypes...>
    operator() ( ArgTypes&&... args ) const {
    return std::invoke(get(), std::forward<ArgTypes>(args)...);
  }

還有一個實現(xiàn)是給存放的參數(shù)是函數(shù)類型使用的,也就是重載"()()",可以調(diào)用這個函數(shù)并傳參過去。

最后就是C++17引入的推導(dǎo)指引,顧名思義就是幫助模板類型推導(dǎo)使用的

 推導(dǎo)指引
template<class T>
reference_wrapper(T&) -> reference_wrapper<T>;

如果沒有這句話,我們構(gòu)造reference_wraper時,需要這么寫reference_wraper<int>(n1),那么有了這句推導(dǎo)指引,我們可以寫成這樣reference_wraper(n1),方便很多,不用寫模板參數(shù)類型。

那么接下來我們調(diào)用試試看(因為cppreference中實現(xiàn)有些語法用到了C++17或者更高,使用編譯器要更高版本或者替換一些語法即可):

void func(int& n2) {
    n2++;
}
int main() {
    int n1 = 0;
    auto bind_fn = std::bind(&func, reference_wrapper(n1));
    bind_fn();
    std::cout << n1 << std::endl;  // 1
}

完美!可以通過, 所以reference_wrapper本質(zhì)是把對象的地址保存, 訪問是取出地址的值。

這里我們借助的是cppreference中實現(xiàn)來講解的,大家也可以參考自己本地編譯器的實現(xiàn)。

4. 為什么使用

我們看下為什么std::bind或者std::thread為什么要使用reference_wrapper,我們以std::bind為例子吧,我們大致去跟蹤下std::bind,跟蹤的目的是看傳遞bound參數(shù)(即我們傳給bind函數(shù)的參數(shù))的生命周期,以vs2019的實現(xiàn)為例:

template <class _Fx, class... _Types>
_NODISCARD _CONSTEXPR20 _Binder<_Unforced, _Fx, _Types...> bind(_Fx&& _Func, _Types&&... _Args) {
    return _Binder<_Unforced, _Fx, _Types...>(_STD forward<_Fx>(_Func), _STD forward<_Types>(_Args)...);
}

看到是構(gòu)造了一個_Binder的對象返回,bound參數(shù)作為構(gòu)造函數(shù)的參數(shù)傳入,

using _Second = tuple<decay_t<_Types>...>; //std::decay_t會移除掉引用屬性
_Compressed_pair<_First, _Second> _Mypair;
constexpr explicit _Binder(_Fx&& _Func, _Types&&... _Args)
        : _Mypair(_One_then_variadic_args_t{}, _STD forward<_Fx>(_Func), _STD forward<_Types>(_Args)...) {}

也可以看到構(gòu)造函數(shù)中,參數(shù)傳遞給_Mypair成員。到這里結(jié)束。

我們再看下調(diào)用時:

#define _CALL_BINDER                                                                  \
    _Call_binder(_Invoker_ret<_Ret>{}, _Seq{}, _Mypair._Get_first(), _Mypair._Myval2, \
        _STD forward_as_tuple(_STD forward<_Unbound>(_Unbargs)...))
template <class... _Unbound>
    _CONSTEXPR20 auto operator()(_Unbound&&... _Unbargs) noexcept(noexcept(_CALL_BINDER)) -> decltype(_CALL_BINDER) {
    return _CALL_BINDER;
}

看到調(diào)用時會用到_CALL_BINDER宏,這里調(diào)用_Call_binder函數(shù),并把_Mypair傳入,再接下來就會調(diào)用到我們的函數(shù)并傳入bound的參數(shù)了。

總結(jié)下就是std::bind首先將傳入的參數(shù)存放起來,等到要調(diào)用bind的函數(shù)就將參數(shù)傳入,而這里沒有保存?zhèn)魅雲(yún)?shù)的引用,只能保存一份參數(shù)的拷貝,如果使用我們上邊說的“int& a = b”語法,_Binder類中無法保存b的引用,自然調(diào)用時傳入的就不是b的引用,所以借助reference_wrapper將傳入?yún)?shù)的地址保存,使用是通過地址取出來值進而調(diào)用函數(shù)。

5. 總結(jié)

我來給總結(jié)下,首先我們講解了std::ref的一些用法,然后我們講解std::ref是通過std::reference_wrapper實現(xiàn),然后我們借助了cppreference上的實現(xiàn)來給大家剖析了他本質(zhì)就是存放了對象的地址(類似指針的用法),還講解了noexcept等語法,最后我們講解了下std::bind為什么要使用到reference_wrapper.

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • C語言詳細(xì)講解if語句與switch語句的用法

    C語言詳細(xì)講解if語句與switch語句的用法

    用 if 語句可以構(gòu)成分支結(jié)構(gòu),它根據(jù)給的條件進行判定,以決定執(zhí)行哪個分支程序段,C 語言中還有另外一種分支語句,就是 switch 語句
    2022-05-05
  • C++ 遞歸、搜索與回溯:三劍客

    C++ 遞歸、搜索與回溯:三劍客

    本文介紹了C++遞歸、搜索、回溯三大核心內(nèi)容,下面用最清晰、最容易理解的方式,一次性講透 C++ 遞歸、搜索、回溯三大核心內(nèi)容,適合學(xué)習(xí)、復(fù)習(xí)、寫題,感興趣的朋友跟隨小編一起看看吧
    2026-04-04
  • C/C++根據(jù)年月日計算星期幾(蔡勒公式篇)

    C/C++根據(jù)年月日計算星期幾(蔡勒公式篇)

    這篇文章主要給大家介紹了關(guān)于C/C++根據(jù)年月日計算星期幾(蔡勒公式篇)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • C++關(guān)于構(gòu)造函數(shù)可向父類或者本類傳參的講解

    C++關(guān)于構(gòu)造函數(shù)可向父類或者本類傳參的講解

    今天小編就為大家分享一篇關(guān)于C++關(guān)于構(gòu)造函數(shù)可向父類或者本類傳參的講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • 一文搞懂c++中的std::move函數(shù)

    一文搞懂c++中的std::move函數(shù)

    這篇文章主要介紹了c++中的std::move函數(shù),在探討c++11中的Move函數(shù)前,先介紹兩個概念(左值和右值),對c++?std::move函數(shù)相關(guān)知識感興趣的朋友一起看看吧
    2022-07-07
  • C語言可變參數(shù)函數(shù)詳解示例

    C語言可變參數(shù)函數(shù)詳解示例

    一般我們編程的時候,函數(shù)中形式參數(shù)的數(shù)目通常是確定的,在調(diào)用時要依次給出與形式參數(shù)對應(yīng)的實際參數(shù)。但在某些情況下我們希望函數(shù)的參數(shù)個數(shù)可以根據(jù)需要確定,因此c語言引入可變參數(shù)函數(shù)。典型的可變參數(shù)函數(shù)的例子有printf()、scanf()等,下面我就開始講解
    2013-11-11
  • C++11新特性之a(chǎn)uto的使用

    C++11新特性之a(chǎn)uto的使用

    熟悉腳本語言的人都知道,很多腳本語言都引入了“類型自動推斷”技術(shù):比如Python,可以直接聲明變量,在運行時進行類型檢查。隨著C++11標(biāo)準(zhǔn)的發(fā)布,C++語言也引入了類型自動推斷的功能。這篇文章主要介紹了C++11新特性之a(chǎn)uto的使用,有需要的朋友們可以參考借鑒。
    2016-12-12
  • C++11特性小結(jié)之decltype、類內(nèi)初始化、列表初始化返回值

    C++11特性小結(jié)之decltype、類內(nèi)初始化、列表初始化返回值

    這篇文章主要介紹了C++11特性小結(jié)之decltype、類內(nèi)初始化、列表初始化返回值,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-05-05
  • C語言 文件的隨機讀寫詳解及示例代碼

    C語言 文件的隨機讀寫詳解及示例代碼

    本文主要介紹C語言 文件的隨機讀寫,這里整理了相關(guān)資料及示例代碼以便大家學(xué)習(xí)參考,學(xué)習(xí)此部分內(nèi)容的朋友可以參考下
    2016-08-08
  • C++實現(xiàn)比較日期大小的示例代碼

    C++實現(xiàn)比較日期大小的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何使用C++實現(xiàn)比較日期大小的功能,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價值,感興趣的可以了解一下
    2023-04-04

最新評論

博客| 登封市| 牟定县| 龙陵县| 舟山市| 罗江县| 江源县| 乌拉特中旗| 东兰县| 平定县| 遂川县| 子长县| 红安县| 石阡县| 玉山县| 左云县| 胶州市| 射洪县| 错那县| 蒙阴县| 淅川县| 崇阳县| 星子县| 开原市| 龙门县| 来宾市| 祁连县| 铁岭县| 大冶市| 吐鲁番市| 聊城市| 开化县| 崇左市| 武山县| 蒙自县| 益阳市| 开阳县| 沙雅县| 浦东新区| 吴江市| 襄垣县|