免费高清特黄a大片,九一h片在线免费看,a免费国产一级特黄aa大,国产精品国产主播在线观看,成人精品一区久久久久,一级特黄aa大片,俄罗斯无遮挡一级毛片

分享

C++11新特性

 xiejblan 2019-11-07

不定參數(shù)函數(shù)

????學(xué)過C語言的人應(yīng)該都用過printf這個庫函數(shù),它的聲明如下:

extern int printf(const char *format,...);

????它的第一個參數(shù)是一個格式化的字符串,后面可以接任意個數(shù)任意類型的參數(shù)(取決于格式化字符串中的格式化字符個數(shù))。如果自定義一個接受不定參數(shù)的函數(shù),改如何實現(xiàn)呢?標(biāo)準(zhǔn)庫有幫助實現(xiàn)這類功能的幫助函數(shù)。

int sum(int count, ...)
{
    va_list vl;
    int sum = 0;
    va_start(vl, count);
    for (int i = 0; i < count; ++i)
    {
        sum += va_arg(vl, int);
    }
    va_end(vl);
    return sum;
}

????上面函數(shù)功能很簡單,第一個輸入是后面不定參數(shù)的個數(shù),然后計算它們的和。借助va_xx系列函數(shù)即可完成。通過va系列函數(shù)操作不定參數(shù)比較麻煩,而且調(diào)用時候也容易出錯(調(diào)用時需要在va_arg函數(shù)中指定類型,而不能通過推導(dǎo)得出類型)。這些弊端在c++11的不定參數(shù)模板里面有了新的解決方案。

不定參數(shù)模板

????在上一個c++標(biāo)準(zhǔn)即c++98標(biāo)準(zhǔn)中模板參數(shù)被要求有確定的個數(shù),而新的c++11標(biāo)準(zhǔn)修改了這一限制,允許代碼編寫者引入不定參數(shù)的模板。這一變化引起了很多標(biāo)準(zhǔn)庫的實現(xiàn),如tuple和bind等,都充分利用了不定參數(shù)模板的語言特性,擺脫了之前“丑陋“的實現(xiàn)方式??匆幌麓a

template <typename... T> class Multityp{};

????上面代碼即一個不定參數(shù)模板類的聲明。這里T被稱為模板參數(shù)包(template parameter pack)。它是一種新的模板參數(shù)類型,有了這樣的參數(shù)類型就可以允許模板參數(shù)接受任意多個不同類型的不同參數(shù)。

Multitype<int, char, double> multi();
Multitype<int> multi2();
Multitype<int, char> multi3();

????使用的代碼很直觀,不需要額外解釋。編譯器會根據(jù)不同場景的調(diào)用(比如上面第一句),將多個參數(shù)打包成一個“單一”的模板參數(shù)T(上面的定義的模板參數(shù)包T),這樣T就是一個包含int,char和double的參數(shù)包亦即類型集合。
????跟普通模板一樣,參數(shù)包也可以是非類型的,即數(shù)值類型。

template <int... NT> class MultitypeInt{};
MultitypeInt<1,2,3> obj; //-->template <int,int,int> class MultitypeInt{};

????在編譯器看來,一個模板參數(shù)包在推導(dǎo)出真正類型前,它仍然是一個參數(shù)(一個打包了n個類型的集合)。如果代碼想應(yīng)用它們時(即希望將它們展開時),這個過程成為解包(unpack)。

template <typename ... T> class Multitype
{
public:
    Multitype(T... params){}
};

????上面代碼中的構(gòu)造函數(shù)參數(shù)就是將模板參數(shù)中的參數(shù)包進(jìn)行解包,解包成為一組對應(yīng)的多類型形參。

不定參數(shù)模板實現(xiàn)的printf

????用不定參數(shù)模板實現(xiàn)這樣功能的函數(shù),實際上基本都要用到遞歸,接下來的代碼實現(xiàn)跟慣常的函數(shù)遞歸方式很類似,比較容易理解,代碼如下:

class print
{
public:
    template<typename FIRST, typename ...PACK>
    static
    void cppprintf(FIRST first, PACK... params)
    {
        std::cout << first;
        cppprintf(params...);
    }

    //重載函數(shù)版本,遞歸結(jié)束條件
    template<typename T>
    static
    void cppprintf(T end)
    {
        std::cout << end << std::endl;
    }
};
print::cppprintf("sdf", 123, 'c', 456, 123.123);

????上述代碼即一個不定參數(shù)的打印函數(shù)實現(xiàn),在筆者的編譯器中,如果不把這兩個函數(shù)聲明在一個class內(nèi),編譯不通過,這個問題我也沒有找到明確的原因。不過不影響這里的演示,通過遞歸的調(diào)用(也需要編譯器做遞歸的查找對應(yīng)調(diào)用函數(shù))實現(xiàn)了一個printf的功能。
????那么這種遞歸推導(dǎo)如何用在類模板中呢?實際上在標(biāo)準(zhǔn)庫的tuple這樣的實現(xiàn)中,就大量用到了這種推導(dǎo),接下來我們來介紹一下。

std::tuple的實現(xiàn)原理

????在講述實現(xiàn)原理前,我們先看一下tuple可以用來做什么。

// tuple example
#include <iostream>    
#include <tuple>       

int main ()
{
  std::tuple<int,char> foo (10,'x');
  auto bar = std::make_tuple ("test", 3.1, 14, 'y');

  std::get<2>(bar) = 100;                                    // access element

  std::get<0>(foo) = std::get<2>(bar);
  std::get<1>(foo) = 'z';

  return 0;
}

????上面代碼應(yīng)比較清楚的展示了tuple的能力,它是一個“元組”,包含了一些不同類型的對象,并且可以通過模板參數(shù)去訪問某一個元組中的對象。除了類型不同外它和vector粗略也比較一下,也比較相像。一個是不定長度的不同類型的集合,另一個是不定長度的想同類型的集合。
????從tuple的功能來看,有兩個重要的點,一個是任意多的類型,一個是任意多的參數(shù)。這個正好符合不定參數(shù)模板參數(shù)的特性。它的實現(xiàn)可以看一下簡化的版本:

template<typename ...T>
class mytuple;

//偏特化版本
template<typename HEAD, typename ...TLIST>
class mytuple<HEAD, TLIST...> : public mytuple<TLIST...>
{
public:
    mytuple(HEAD head, TLIST... args) : mytuple<TLIST...>(args...), value(head)
    {
    }

    HEAD value;
};

//結(jié)束條件,特化版本
template<>
class mytuple<>
{
};

????利用不定參數(shù)模板,定義三個class,分別是版本類定義mytuple,和它的兩個偏特化、特化版本。其中偏特化版本遞歸的繼承了自身,特化版本是遞歸繼承的結(jié)束條件。這樣一個不定模板參數(shù)的類定義就會形成一個n層深的集成關(guān)系,每一層會有對應(yīng)類型的一個成員變量value。那么這樣整個數(shù)據(jù)的模型就形成了。舉一個例子,mytuple<int, char, float>的對象內(nèi)存模型是什么樣子呢?如下圖


tuple.png

????上圖顯示了mytuple<int, char, float>類型的一個對象的內(nèi)存模型,比較清晰就不多做解釋了,tuple正是利用這種遞歸繼承形成的對象一層層存儲不同類型數(shù)據(jù)的。
????那么如何像標(biāo)準(zhǔn)庫函數(shù)一樣把它里面的元素get出來呢?看一下下面的代碼

template<int N, typename ...T>
struct mytupleat;

//類模板偏特化
template<int N, typename T, typename ...TLIST>
struct mytupleat<N, mytuple<T, TLIST...> >
{
    static_assert(sizeof...(TLIST) >= N, "wrong index");
    typedef typename mytupleat<N - 1, mytuple<TLIST...> >::value_type value_type;
    typedef typename mytupleat<N - 1, mytuple<TLIST...> >::tuple_type tuple_type;
};

//類模板偏特化
template<typename T, typename ...TLIST>
struct mytupleat<0, mytuple<T, TLIST...> >
{
    typedef T value_type;
    typedef mytuple<T, TLIST...> tuple_type;
};
///////////////////////////////////////////////////////////////////////////
template<int N, typename HEAD, typename ...TLIST>
typename mytupleat<N, mytuple<HEAD, TLIST...> >::value_type
mygettuple(mytuple<HEAD, TLIST...> tuple)
{
    typedef typename mytupleat<N, mytuple<HEAD, TLIST...> >::value_type VALUE;
    typedef typename mytupleat<N, mytuple<HEAD, TLIST...> >::tuple_type TUPLE;
    VALUE ret = ((TUPLE) tuple).value;
    return ret;
}
///////////////////////////////////////////////////////////////////////////
class TestMyTuple
{
public:
    static void execute()
    {
        mytuple<int, double, const char *> test(12, 13.12, "123");

        auto ntest = mygettuple<0>(test);
        auto dtest = mygettuple<1>(test);
        auto csztest = mygettuple<2>(test);

        mytuple<int> test2(22);
        auto ntest2 = mygettuple<0>(test2);
    }
};

????mytupeat類是用來通過類模板和其偏特化版本去遞歸定義某一個N(即類型index)對應(yīng)的對象類型和mytuple的類型(是哪一層的基類)。結(jié)束條件是N = 0的情況,這時候兩個typedef的類型最終找到了最后定義的類型。舉例說明,N = 0的情況,直接應(yīng)用偏特化版本struct mytupleat<0, mytuple<T, TLIST...> >,兩個類型都找到了確切的定義;N > 0的情況,假如N = 2,當(dāng)遞歸的找typedef的過程,N - 1 = 0時遇到邊界,定義的T問value_type,此時的T正好是index = 2時候的類型,同理tuple_type亦然。
???? mygettuple這個模板函數(shù)用來最終完成取出某一個index位置上的某一個類型的value。從調(diào)用反推來看一下,拿測試代碼的mygettuple<1>(test)這一句舉例,首先根據(jù)test對象的類型推導(dǎo)出mygettuple輸入?yún)?shù)的類型是mytuple<int, double, const char * >亦即mytuple<int, T...>,繼而返回值類型為mytupleat<1, mytuple<int, double, const char* > >::value_type,函數(shù)內(nèi)部的兩個typedef類型分別為mytupleat<1, mytuple<int, double, const char* > >::value_type和mytupleat<1, mytuple<int, double, const char* > >::tuple_type。上述這些類型推導(dǎo)還需要各自遞歸推導(dǎo),最終value_type-->mytupleat<0, mytuple<double, const char* > >::value_type
tuple_type->mytupleat<0, mytuple<double, const char* > >::tuple_type。
類型推導(dǎo)出來后,很容易拿到相應(yīng)對象中的value了。
????以上就是tuple的簡單實現(xiàn)原理,標(biāo)準(zhǔn)庫的實現(xiàn)更為復(fù)雜和全面,這里筆者只是用最簡化的代碼來展現(xiàn)如何運用不定參數(shù)模板以及應(yīng)用它的技巧來實現(xiàn)一些炫酷的功能。

std::bind實現(xiàn)原理

????首先看一下bind用來做什么的。在c++98標(biāo)準(zhǔn)中標(biāo)準(zhǔn)庫里的bind功能是通過bind1st和bind2nd兩個模板函數(shù)實現(xiàn)的。它們的作用不贅述了,c++11標(biāo)準(zhǔn)庫直接廢棄了它們,用不定參數(shù)模板實現(xiàn)了bind來代替它們,現(xiàn)在的bind函數(shù)能力更強,更易用。那么bind用來做什么的?簡單說它可以將函數(shù)(函數(shù)指針、functor、lambda)和函數(shù)所需的參數(shù)(任意個數(shù)參數(shù))綁定為一個對象,我們在后續(xù)用到的時候可以直接調(diào)用這個對象的operator()函數(shù)即可實現(xiàn)對這個函數(shù)的調(diào)用。這個功能在某些場景極為方便,舉個例子,后面我們將要介紹std::thread,并且要實現(xiàn)一個基于它的線程池,那么這個線程池需要動態(tài)傳入每次線程執(zhí)行的函數(shù)和函數(shù)參數(shù),利用bind我們可以每次傳入不同類型的函數(shù),每一次函數(shù)調(diào)用的參數(shù)和類型都可以完全不同,極大的方便調(diào)用者使用,充分展現(xiàn)c++11標(biāo)準(zhǔn)帶來的編程便利?;氐絙ind,我們看一段展示bind功能的代碼

    static void execute()
    {
        auto f = std::bind([](int a, char b, const char *c)
                           {
                               std::cout << "bindtest:" << a << b << c << std::endl;
                           }, 1, 'a', "abc");
        f();
    }

????上面代碼直接調(diào)用std::bind返回一個綁定對象。傳給bind的第一參數(shù)是函數(shù)(lambda),后面三個參數(shù)就是前面函數(shù)需要的三個參數(shù),通過bind將它們綁定在一起。后面需要的時候調(diào)用f()即可完成調(diào)用。bind還有很多其他特性,比如用placeholder去改變調(diào)用參數(shù)位置等,這里不介紹這些更靈活的特性了,我們來看一下要實現(xiàn)bind的基本功能需要做些什么。首先我們需要定義一個類,這個類就是bind的返回值,我們將bind傳入的參數(shù)都保存在這個類中,由這個類完成函數(shù)的存儲和函數(shù)所需參數(shù)的存儲,需要調(diào)用的時候,通過這個類的operator()函數(shù)來動態(tài)調(diào)用之前傳入的函數(shù)。原理很簡單,需要解決的問題是1、如何將不定參數(shù)存儲起來2、調(diào)用的時候如何按參數(shù)順序?qū)⒅按鎯Φ膮?shù)展開傳給函數(shù)。直接看代碼

//通過遞歸方式實現(xiàn)的一個簡易的makeindex,
template<std::size_t ...>
struct IndexTuple{};


template<std::size_t N, std::size_t... Indexes>
struct MakeIndexes : MakeIndexes<N - 1, N - 1, Indexes...>{};


//遞歸繼承結(jié)束條件, 同時也是對上面模板的一個偏特化
template<std::size_t... indexes>
struct MakeIndexes<0, indexes...>
{
    typedef IndexTuple<indexes...> type;
};

//--------------------------------------------------------------

template <typename Fn, typename ...Params>
class _MybindImpl
{
public:
    typedef typename MakeIndexes<sizeof...(Params)>::type __my_indices;

    Fn _fn;
    std::tuple<Params...> _params;

    _MybindImpl(Fn&& fn, Params&&... params) : _fn(std::forward<Fn>(fn)), _params(std::forward<Params>(params)...)
    {

    }

    void operator()()
    {
        return invoke(__my_indices());
    }

    template <std::size_t ..._Indx>
    void invoke(IndexTuple<_Indx...>)
    {
        _fn(std::get<_Indx>(_params)...);
    }
};

template<typename Fn, typename ...Params>
_MybindImpl<Fn, Params...> MyBind(Fn &&fn, Params &&... params)
{
    return _MybindImpl<Fn, Params...>(std::forward<Fn>(fn), std::forward<Params>(params)...);
}

class BindTest
{
public:
    static void execute()
    {
        auto f = std::bind([](int a, char b, const char *c)
                           {
                               std::cout << "bindtest:" << a << b << c << std::endl;
                           }, 1, 'a', "abc");
        f();

        int teswt = 12;
        auto f2 = MyBind([](int a, char b, const char *c)
                         {
                             std::cout << "Mybindtest:" << a << b << c << std::endl;
                         }, teswt, 'a', "abc");
        f2();
    }
};

????這段代碼極簡化的展現(xiàn)了bind的實現(xiàn)原理,這里忽略了很多細(xì)節(jié),而且也沒有處理函數(shù)的返回值,如果對其他細(xì)節(jié)有興趣可以自行查閱bind的標(biāo)準(zhǔn)庫實現(xiàn)。上面提出的問題1很好解決,用tuple完美解決。那么問題2呢?MakeIndexes就是我們自己實現(xiàn)的調(diào)用時將tuple參數(shù)全部展開的一個工具類,然后通過_MybindImpl的invoke最終在目標(biāo)函數(shù)調(diào)用中將所有參數(shù)展開。
????MakeIndexes的模板和它的偏特化版本實際就是在利用遞歸條件的結(jié)束,將最終的參數(shù)index序列推導(dǎo)出來。舉例子,MakeIndexes<3> --> MakeIndexes<3> : MakeIndexes<2, 2> -> MakeIndexes<2, 2> : MakeIndexes<1, 1, 2> -> MakeIndexes<1, 1, 2> : MakeIndexes<0, 0, 1, 2>(結(jié)束條件) -> typedef IndexTuple<0, 1, 2> type;
上面的推導(dǎo)過程很清晰了,最終實際就是需要一個IndexTuple的類型而已。
后續(xù)通過invoke函數(shù)將IndexTuple攜帶的不定模板參數(shù)展開,展開傳遞給std::get,這樣就完成了調(diào)用目標(biāo)函數(shù)傳入任意參數(shù)的問題。我們再看一下invoke這個函數(shù)

    template <std::size_t ..._Indx>
    void invoke(IndexTuple<_Indx...>)
    {
        _fn(std::get<_Indx>(_params)...);
    }

????std::get<_Indx>(_params)...這種調(diào)用稱為參數(shù)組展開(Parameter Pack Expansion),這個用法前面沒有遇到過,c++11這種方便的語義,使程序有了很強大的復(fù)雜語義實現(xiàn)的能力。
????MakeIndexes所要實現(xiàn)的功能,在c++14標(biāo)準(zhǔn)庫中有了標(biāo)準(zhǔn)實現(xiàn)--std::integer_sequence,有興趣的同學(xué)可以查閱一下。

最后

????上面簡單介紹了c++11的不定參數(shù)模板,以及其強悍語法帶來的諸多語言層面上的提升,同時也簡要介紹了一下tuple和bind的實現(xiàn)原理,接下來的文章中會用到它們?nèi)崿F(xiàn)更為實用的功能,到那時候應(yīng)當(dāng)可以充分體驗c++11帶來的便捷的編程體驗。

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

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多