溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Visual C++ 2015引入更新的C++特性有哪些

發(fā)布時(shí)間:2021-11-30 16:53:10 來源:億速云 閱讀:144 作者:iii 欄目:編程語言

這篇文章主要講解了“Visual C++ 2015引入更新的C++特性有哪些”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Visual C++ 2015引入更新的C++特性有哪些”吧!

Visual C++ 2015 是 C++ 團(tuán)隊(duì)付出巨大努力將現(xiàn)代C++引入windows平臺(tái)的成果。在***的幾個(gè)發(fā)行版本里,VC++已經(jīng)逐步添加了現(xiàn)代C++語言以及庫的特色,這些結(jié)合在一起會(huì)創(chuàng)造一個(gè)用于構(gòu)建通用windows App和組件的絕對(duì)驚艷的開發(fā)環(huán)境。Visual C++2015建立在早期版本引入的驚人進(jìn)步,提供了成熟的、支持大多數(shù)C++11特性以及C++ 2015子集的編譯器。你或許會(huì)懷疑編譯器支持的完整程度,公正地說,我認(rèn)為他能支持大部分重要的語言特性,支持現(xiàn)代C++將會(huì)迎來windows 程序庫開發(fā)一片新的天地。這才是關(guān)鍵。只要編譯器支持一個(gè)高效優(yōu)雅的庫的開發(fā)環(huán)境,開發(fā)者就能構(gòu)建偉大的app和組件。

這里我不會(huì)讓你看一個(gè)枯燥的新特性列表,或者走馬觀花地看下它的功能,而是會(huì)帶你瀏覽下一些傳統(tǒng)情況下的復(fù)雜代碼現(xiàn)在如何讓人相當(dāng)愉快書寫。當(dāng)然,這得益于成熟的Visual C++編譯器。我將會(huì)向你展示windows的一些本質(zhì),在現(xiàn)在或?qū)鞟PI中實(shí)際上都是很重要的本質(zhì)。

頗具諷刺意味的是,對(duì)于COM來說,C++已經(jīng)足夠現(xiàn)代了.是的,我在談?wù)摻M件對(duì)象模型(COM),多年以來,它一直是大多數(shù)Windows API的基石.同時(shí),它也繼續(xù)作為Windows運(yùn)行時(shí)的基石.COM無可爭辯的依附于C++的原始設(shè)計(jì),借鑒了許多來自C++的二進(jìn)制和語義約定,但是它從來都不夠優(yōu)雅.C++的部分內(nèi)容被認(rèn)為可移植性不夠,如dynamic_cast,必須避免使用它,以采用可移植的解決方案,這使得C++的開發(fā)實(shí)現(xiàn)更具挑戰(zhàn)性.近些年已經(jīng)為C++開發(fā)者提供了許多解決方案,讓COM變得更加可移植.C++/CX 語言拓展,可能是Visual C++團(tuán)隊(duì)到目前為止***野心的.具有諷刺意味的是,這些提升標(biāo)準(zhǔn)C++支持的努力,已經(jīng)將C++/CX棄之不顧了,也讓語言拓展變得冗余.

為了證明這點(diǎn),我會(huì)展示給你如何完整的用現(xiàn)代C++實(shí)現(xiàn)IUnknown和IInspectable接口.關(guān)于這兩個(gè)接口沒有什么現(xiàn)代的或吸引力的東西.IUnknown繼續(xù)成為卓越API,如DirectX,的集中抽象.這些接口--IInspectable繼承自IUnknown--位于Windows運(yùn)行時(shí)的中心.我將展示給你如何不用任何語言拓展來實(shí)現(xiàn)它們,接口表或其它宏--只需要包含大量類型信息的高效和優(yōu)雅的C++,就可以讓編譯器和開發(fā)者擁有,關(guān)于如何創(chuàng)建所需的,優(yōu)異的人機(jī)對(duì)話.

主要的問題是, 如何列出  COM 或 Windows Runtime 類需要實(shí)現(xiàn)的接口, 而且要方便開發(fā)者使用, 和編譯器訪問. 比如, 列出所有可用類型, 以便編譯器查詢, 甚至枚舉出相應(yīng)的接口. 要是能實(shí)現(xiàn)這樣的功能, 也許就能讓編譯器生成 IUnknown QueryInterface 甚至 IInspectable GetIids 方法的代碼. 這兩個(gè)方法才是問題的關(guān)鍵. 按照傳統(tǒng)的觀念, 唯一的解決辦法涉及到語言擴(kuò)展(language extensions), 萬惡的宏定義, 以及一堆難以維護(hù)的代碼.

兩種方法的實(shí)現(xiàn), 都用到類需要實(shí)現(xiàn)的接口. 可變參數(shù)模板( variadic template)是***:

template <typename ... Interfaces>  class __declspec(novtable) Implements : public Interfaces ...  {  };

__declspec(novtable)拓展屬性可以防止構(gòu)造函數(shù)和析構(gòu)函數(shù)初始化抽象類的vfptr,這通常意味著減少大量的代碼.實(shí)現(xiàn)類模板包括一個(gè)模板參數(shù)包,這使它成為一個(gè)可變模板.一個(gè)參數(shù)包即一個(gè)模板參數(shù)接受任意數(shù)目的模板參數(shù)變量.但是在這種情況下,我描述的模板參數(shù)將只會(huì)在編譯時(shí)進(jìn)行查詢.接口將不會(huì)出現(xiàn)在函數(shù)的參數(shù)列表之中.

這些參數(shù)的一個(gè)使用已經(jīng)顯而易見.參數(shù)包拓展后成為公共基礎(chǔ)類的參數(shù)列表.當(dāng)然,我仍然有責(zé)任到***實(shí)現(xiàn)這些虛函數(shù),但是此刻我會(huì)描述一個(gè)實(shí)現(xiàn)任意數(shù)目接口的一個(gè)具體類:

class Hen : public Implements<IHen, IHen2>  {  };

因?yàn)閰?shù)包拓展為指定基礎(chǔ)類的列表,所有它等同于下面我可能會(huì)寫出的代碼:

class Hen : public IHen, public IHen2  {  };

用這種方式結(jié)構(gòu)化實(shí)現(xiàn)類模板的美妙之處在于,我現(xiàn)在可以,在實(shí)現(xiàn)類模板中,寫入各種樣版實(shí)現(xiàn)代碼,而Hen類的開發(fā)者則可以使用這種不唐突的抽象,同時(shí)大量忽略隱含的細(xì)節(jié).

到目前為止,一切都很好.現(xiàn)在,我將考慮IUnknown的實(shí)現(xiàn).我應(yīng)該可以在實(shí)現(xiàn)類模板中完整的實(shí)現(xiàn)它,并提供編譯器現(xiàn)在所擁有的類型信息.IUnknown提供了對(duì)于COM類非常重要的兩種工具,就像氧氣和水對(duì)于人類一樣.***個(gè)可能簡單些的是引用計(jì)數(shù),這也是COM對(duì)象跟蹤它們生命周期的方式.COM規(guī)定一種侵入式的引用計(jì)數(shù),它借助于每個(gè)對(duì)象,統(tǒng)計(jì)多少個(gè)外部引用存在,來負(fù)責(zé)管理自己的生命周期.這與智能指針,如C++ 11的shared_ptr類,的引用計(jì)數(shù)恰恰相反,智能指針對(duì)象并不知道它的共享關(guān)系.你可能會(huì)爭論這兩種方式的優(yōu)缺點(diǎn).但是,實(shí)際上COM的方法通常更高效,這也是COM的工作方式,你必須處理它.如果沒有其它的,你很可能會(huì)同意這點(diǎn),在shared_ptr里面包裝一個(gè)COM接口會(huì)是一件極不友好的事情!

我將以只有運(yùn)行時(shí)的開銷作為開始,它是通過實(shí)現(xiàn)類模板介紹的:

protected:    unsigned long m_references = 1;    Implements() noexcept = default;    virtual ~Implements() noexcept    {}

默認(rèn)構(gòu)造函數(shù)并不是真正的開銷所在,它只是簡單的確保最終的構(gòu)造函數(shù)--它將初始化引用計(jì)數(shù)--為protected而不是public的.引用計(jì)數(shù)和虛構(gòu)造函數(shù)都是protected的.讓派生類訪問引用計(jì)數(shù),是為了允許更復(fù)雜的類組合.大多數(shù)類可以簡單的忽略它,但是需要注意的是,我正初始化引用計(jì)數(shù)為1.這和通常建議初始化引用計(jì)數(shù)為0,形成鮮明的對(duì)比,因?yàn)榇藭r(shí)并沒有處理引用.這個(gè)方式在ATL中非常流行,明顯受到Don Box的COM本質(zhì)論的影響,但是這是非常有問題的,ATL的源代碼的研究可以作為佐證.開始于這個(gè)假設(shè),即引用的所有權(quán)將會(huì)立即由調(diào)用者獲得,或者依附于一個(gè)提供更少錯(cuò)誤構(gòu)造處理的智能指針.

虛析構(gòu)函數(shù)提供了很大的便利性,它允許實(shí)現(xiàn)類模板實(shí)現(xiàn)引用計(jì)數(shù),而不是強(qiáng)制實(shí)現(xiàn)類本身來提供實(shí)現(xiàn).另一個(gè)選項(xiàng),是使用奇特的遞歸模板模式(Curiously Recurring Template Pattern)來避免使用虛函數(shù).通常我會(huì)選擇這個(gè)方法,但是它會(huì)稍微增加抽象的復(fù)雜性,同時(shí),因?yàn)镃OM類本身有一個(gè)vtable,所以這里也沒有什么理由去避免使用虛函數(shù).有了這些基本類型之后,在實(shí)現(xiàn)類模板中實(shí)現(xiàn)AddRef和Release將會(huì)變得非常簡單.首先,AddRef方法可以簡單的使用InterlockedIncrement來增加引用計(jì)數(shù):

virtual unsigned long __stdcall AddRef() noexcept override  {    return InterlockedIncrement(&m_references);  }

這不言自明.不要想出某些復(fù)雜的方法,通過使用C++的加減操作符來有條件的替換InterlockedIncrement和InterlockedDecrement函數(shù).ATL通過極大的增加復(fù)雜性去做這個(gè)嘗試.如果你考慮效率,寧可為避免調(diào)用AddRef和Release產(chǎn)生謬誤而多花心思.同樣的,現(xiàn)代C++增加了對(duì)move語義的支持,以及增加轉(zhuǎn)移引用所有權(quán)的能力.現(xiàn)在,Release方法只是略顯復(fù)雜:

virtual unsigned long __stdcall Release() noexcept override  {    unsigned long const remaining = InterlockedDecrement(&m_references);    if (0 == remaining)    {      delete this;    }    return remaining;  }

引用計(jì)數(shù)減少后,結(jié)果被賦值給臨時(shí)變量.這很重要,因?yàn)榻Y(jié)果需要返回.但是如果對(duì)象銷毀了,引用此對(duì)象的成員變量就是非法的了.假定沒有其它未處理的引用,這個(gè)對(duì)象就通過前面說到的虛析構(gòu)函數(shù)刪除了.這就是引用計(jì)數(shù)的結(jié)論,實(shí)現(xiàn)類Hen仍然和之前的一樣簡單:

class Hen : public Implements<IHen, IHen2>  {  };

現(xiàn)在,到了想象一下QueryInterface的奇妙世界的時(shí)間了。實(shí)現(xiàn)IUnknown方法是一個(gè)很重要的實(shí)踐。在我的Pluralsight課程中,我廣泛的實(shí)現(xiàn)了它。你可以在Don Box編寫的<<COM本質(zhì)論>>(Addison-Wesley Professional,1998)一書中,閱讀關(guān)于實(shí)現(xiàn)你自己的IUnknown的奇妙的和不可思議的方法。需要注意的是,雖然這是一本關(guān)于COM的優(yōu)秀書籍,但是它是基于C++98的,并沒有呈現(xiàn)出任何現(xiàn)代C++的特征。為了節(jié)省時(shí)間,我假定你已經(jīng)熟悉了QueryInterface的實(shí)現(xiàn)過程,并集中于如何用現(xiàn)代C++實(shí)現(xiàn)它。下面是虛函數(shù)本身:

virtual HRESULT __stdcall QueryInterface(    GUID const & id, void ** object) noexcept override  {  }

給定一個(gè)GUID用來標(biāo)識(shí)一個(gè)特別的接口之后,QueryInterface應(yīng)該來決定一個(gè)對(duì)象是否實(shí)現(xiàn)了需要的接口。如果實(shí)現(xiàn)了,它必須減少這個(gè)對(duì)象的引用計(jì)數(shù),同時(shí)通過外部參數(shù)來返回所需的接口指針。如果沒有實(shí)現(xiàn),它必須返回一個(gè)空指針。因此,我將以一個(gè)粗略的輪廓來作為開始:

*object = // Find interface somehow  if (nullptr == *object)  {    return E_NOINTERFACE;  }  static_cast<::IUnknown *>(*object)->AddRef();  return S_OK;

QueryInterface首先會(huì)嘗試設(shè)法查找所需的接口。如果接口受不支持,則返回E_NOINTERFACE錯(cuò)誤碼。請(qǐng)注意,我是如何按照要求處理接口指針不支持的情況。你應(yīng)該把QueryInterface接口看作是二元的操作。它要么成功找到所需的接口,要么查找失敗。不要嘗試發(fā)揮創(chuàng)造性,只需要依據(jù)條件響應(yīng)即可。盡管COM規(guī)范有一些限制項(xiàng),但是大多數(shù)消費(fèi)者都會(huì)簡單的假定接口不受支持,而不管你會(huì)返回何種錯(cuò)誤碼。在你的實(shí)現(xiàn)中的任何錯(cuò)誤,都毫無疑問的會(huì)導(dǎo)致你陷入調(diào)試的深淵。QueryInterface是非常基礎(chǔ)的,不能胡亂對(duì)待。***,AddRef由接口指針再次調(diào)用,用來支持某種極少的而又允許的類組合場(chǎng)景。這些不受實(shí)現(xiàn)類模板的顯式支持,但是我情愿在這里做一個(gè)表率。重要的是,記住引用計(jì)數(shù)操作是面向接口的,而不是面向?qū)ο蟮摹D悴荒?簡單的,在屬于一個(gè)對(duì)象的任意接口上面,調(diào)用AddRef或者Release。你必須依賴COM規(guī)則來管理對(duì)象,否則你會(huì)冒險(xiǎn)引入以不可思議的方式崩潰的非法代碼。

但是我如何得知,請(qǐng)求的GUID是否就代表著類想要實(shí)現(xiàn)的接口呢?我需要回到實(shí)現(xiàn)類模板收集的類型信息的地方,其中類型信息通過它的模板參數(shù)包來收集。請(qǐng)記住,我的目標(biāo)是準(zhǔn)許編譯器為我實(shí)現(xiàn)它。我希望最終代碼,和我手寫的一樣高效,甚至更好。我會(huì)通過可變函數(shù)模板集合來進(jìn)行查詢,函數(shù)模板自身包括模板參數(shù)包。我將以BaseQueryInterface函數(shù)模板作為開始:

virtual HRESULT __stdcall QueryInterface(    GUID const & id, void ** object) noexcept override  {    *object = BaseQueryInterface<Interfaces ...>(id);

BaseQueryInterface本質(zhì)上是IUnknown QueryInterface的現(xiàn)代C++版本。它直接返回接口指針而不是HRESULT類型??罩羔槃t表明失敗的情況。它接受單一函數(shù)參數(shù),GUID標(biāo)識(shí)著要查找的接口。更重要的是,我拓展了類模板參數(shù)包為完整模式,這樣,BaseQueryInterface函數(shù)就可以開始枚舉接口的處理過程。 起初你可能會(huì)認(rèn)為,由于BaseQueryInterface是實(shí)現(xiàn)類模板的成員函數(shù),所以它可以簡單直接的訪問接口的鏈表,但是我需要準(zhǔn)許這個(gè)函數(shù)剝離鏈表中的***個(gè)接口,就像下面這樣:

template <typename First, typename ... Rest>  void * BaseQueryInterface(GUID const & id) noexcept  {  }

按照這種方式,BaseQueryInterface函數(shù)能識(shí)別***個(gè)接口,并且給接下來的搜索留有余地。看吧,COM有一定數(shù)量的特殊規(guī)則來支持QueryInterface 實(shí)現(xiàn)或至少接受對(duì)象識(shí)別。尤其是請(qǐng)求IUnknown,必須總是返回確切相同的指針,客戶端才能確定兩個(gè)接口的指針是否來自同一個(gè)對(duì)象。因此,BaseQueryInterface函數(shù)最棒的地方就是實(shí)現(xiàn)了這些假設(shè)。所以,可以從***代表類想要實(shí)現(xiàn)的***個(gè)接口的模板參數(shù)的GUID請(qǐng)求的對(duì)比開始。如果不匹配,我會(huì)檢查IUnknown是否開始請(qǐng)求了:

if (id == __uuidof(First) || id == __uuidof(::IUnknown))  {    return static_cast<First *>(this);  }

假設(shè)有一個(gè)匹配的,我直接準(zhǔn)確無誤的返回了***個(gè)接口的指針。static_cast 能確保編譯器基于IUnknown的多種接口不會(huì)引起歧義。cast只是校準(zhǔn)了指針,讓類的vtable能找到正確的指針位置,因?yàn)樗衯table接口是以IUnknown的三個(gè)方法開始的,這非常符合邏輯。

而我不妨同樣添加IInspectable查詢的可選支持。IInspectable相當(dāng)變態(tài),在某種意義上,它是Windows運(yùn)行時(shí)接口,因?yàn)槊總€(gè)Windows運(yùn)行時(shí)預(yù)計(jì)編程語言(如 C# 和 JavaScript)必須直接來自IInspectable,而不僅僅只是IUnknown接口。相對(duì)于C++的工作方式和COM傳統(tǒng)的定義方式,以適應(yīng)公共語言運(yùn)行庫的方式實(shí)現(xiàn)對(duì)象和接口是不幸的事實(shí)。更不幸的是當(dāng)對(duì)象組合的時(shí)候?qū)π阅艿挠绊懀視?huì)在下文中討論。至于QueryInterface,我只需確保IInspectable能被查詢,它應(yīng)該是一個(gè)Windows運(yùn)行時(shí)類的實(shí)現(xiàn),而不是一個(gè)簡單的典型COM類。雖然關(guān)于IUnknown的明確的COM規(guī)則不適用于IInspectable,我可以簡單的用相同的方式對(duì)待后者。但這兩個(gè)挑戰(zhàn)。首先,需要了解是否有任何IInspectable派生出來的接口實(shí)現(xiàn)。第二,需要了解接口的類型,這樣就可以正確的返回一個(gè)沒有歧義的調(diào)整過的接口指針。假定列表中的***個(gè)接口都是基于IInspectable,那可以只更新BaseQueryInterface 如下:

if (id == __uuidof(First) ||    id == __uuidof(::IUnknown) ||    (std::is_base_of<::IInspectable, First>::value &&    id == __uuidof(::IInspectable)))  {    return static_cast<First *>(this);  }

注意,我用的是C++ 11中的is_base_of 的特性,來確定***個(gè)模板參數(shù)是一個(gè)IInspectable的衍生接口。萬一實(shí)現(xiàn)典型的COM類不支持Windows運(yùn)行時(shí),就能確保隨后的對(duì)照是由編譯器排除的。這樣我可以無縫地支持Windows運(yùn)行時(shí)和經(jīng)典的COM類,即沒有增加組件開發(fā)人員的語句復(fù)雜性,也沒有任何不必要的運(yùn)行時(shí)開銷。但是,如果恰好遇列舉出來得首位不是IInspectable接口,就會(huì)有不容易察覺的Bug的隱患。所需要做的就是,用某種方法替代is_base_of來掃描整個(gè)接口的列表:

template <typename First, typename ... Rest>  constexpr bool IsInspectable() noexcept  {    return std::is_base_of<::IInspectable, First>::value ||      IsInspectable<Rest ...>();  }

IsInspectable 也是基于is_base_of特性的,但是當(dāng)前適用于匹配接口。如果沒找到基于IInspectable 的接口則終止:

template <int = 0>  constexpr bool IsInspectable() noexcept  {    return false;  }

我會(huì)禁用掉稀奇古怪的默認(rèn)參數(shù)。假定 IsInspectable 返回的是 true,我需要找到***個(gè)IInspectable-based 接口:

template <int = 0>  void * FindInspectable() noexcept  {    return nullptr;  }  template <typename First, typename ... Rest>  void * FindInspectable() noexcept  {    // Find somehow  }

再次使用 is_base_of 特性,但這次要返回一個(gè)真實(shí)匹配的接口指針:

#pragma warning(push)  #pragma warning(disable:4127) // conditional expression is constant  if (std::is_base_of<::IInspectable, First>::value)  {    return static_cast<First *>(this);  }  #pragma warning(pop)  return FindInspectable<Rest ...>();

BaseQueryInterface 這時(shí)可以利用IsInspectable 和 FindInspectable 一起來支持查詢 IInspectable:

if (IsInspectable<Interfaces ...>() &&     id == __uuidof(::IInspectable))  {    return FindInspectable<Interfaces ...>();  }

然后指定具體的 Hen 類:

class Hen : public Implements<IHen, IHen2>  {  };

實(shí)現(xiàn)類的模板,可以確保編譯器能生成更高效的代碼,不管 IHen、Hen2 來自 IInspectable 還是 IIUnknown (或者其他接口)?,F(xiàn)在,我可以***實(shí)現(xiàn) QueryInterface 的遞歸部分,以及任何追加的接口,例如上面例子中的 IHen2。BaseQueryInterface 是靠調(diào)用 FindInterface 函數(shù)模板結(jié)束的:

template <typename First, typename ... Rest>  void * BaseQueryInterface(GUID const & id) noexcept  {    if (id == __uuidof(First) || id == __uuidof(::IUnknown))    {      return static_cast<First *>(this);    }    if (IsInspectable<Interfaces ...>() &&       id == __uuidof(::IInspectable))    {      return FindInspectable<Interfaces ...>();    }    return FindInterface<Rest ...>(id);  }

注意,我調(diào)用這個(gè)FindInterface函數(shù)模板,大致等同于我原來調(diào)用的BaseQueryInterface,在這個(gè)例子中,我向它傳遞接口的其余部分。我特意再次擴(kuò)大參數(shù)包,這樣它可以在列表的其余部分識(shí)別***接口。但會(huì)提示一個(gè)故障。由于模板參數(shù)包不是以函數(shù)實(shí)參來擴(kuò)展的,這可能會(huì)變得棘手,編程語言寫不出來我想要的。更多的時(shí)候,這種“遞歸的”FindInterface可變模板正是你想要的:

template <typename First, typename ... Rest>  void * FindInterface(GUID const & id) noexcept  {    if (id == __uuidof(First))    {      return static_cast<First *>(this);    }    return FindInterface<Rest ...>(id);  }

它會(huì)從模板參數(shù)的其余部分中分離,如果有匹配就返回調(diào)整過的接口指針。另外,它也會(huì)調(diào)用自己,直到list取完。當(dāng)我籠統(tǒng)地提及編譯期遞歸時(shí),重要的是要注意這個(gè)函數(shù)模板,以及其他類似的實(shí)現(xiàn)類模板的例子,在技術(shù)上遞歸,而不是在編譯期。每個(gè)函數(shù)模板的實(shí)例調(diào)用不同的函數(shù)模板的實(shí)例。例如,F(xiàn)indInterface<IHen, IHen2> 調(diào)用 FindInterface<IHen2>, FindInterface<IHen2>調(diào)用 FindInterface<>。為了讓它遞歸, FindInterface<IHen, IHen2>不需要調(diào)用FindInterface<IHen, IHen2>。

盡管如此,還是要記住,這樣的“遞歸”發(fā)生在編譯時(shí),它就像你自己手寫的一條條if語句。但是,現(xiàn)在,我遇到麻煩了。這個(gè)序列如何終止呢?當(dāng)然是當(dāng)模板參數(shù)列表為空的時(shí)候。這個(gè)問題在于C++已經(jīng)定義了空模板參數(shù)列表的含義:

template <>  void * FindInterface(GUID const &) noexcept  {    return nullptr;  }

這幾乎是正確的,但是編譯器會(huì)提示你,函數(shù)模板在這個(gè)特化中無法使用。同時(shí),如果我不提供終止函數(shù),當(dāng)參數(shù)包為空的時(shí)候,編譯器將無法編譯最終的調(diào)用。這不是函數(shù)重載的情況,因?yàn)閰?shù)列表依舊是相同的。幸運(yùn)的是,解決方案非常簡單。我可以通過提供一個(gè)無名的默認(rèn)參數(shù),來避免終止函數(shù)看起來像一個(gè)特化:

template <int = 0>  void * FindInterface(GUID const &) noexcept  {    return nullptr;  }

編譯器樂于此,同時(shí),如果請(qǐng)求一個(gè)不支持的接口,終止函數(shù)會(huì)簡單的返回一個(gè)空指針,同時(shí)虛函數(shù)QueryInterface將返回E_NOINTERFACE錯(cuò)誤碼。對(duì)IUnknown而言,這考慮得很周到。如果你所關(guān)心的是經(jīng)典的COM,你可以安全的停在那里,因?yàn)槟蔷褪悄闼枰乃袃?nèi)容。關(guān)于這點(diǎn),可以反復(fù)的操作,編譯器將優(yōu)化QueryInterface的實(shí)現(xiàn),其間使用各種各樣的“遞歸的”函數(shù)調(diào)用和常量表達(dá)式,代碼至少和你手工寫的一樣好。對(duì)于IInspectable而言,同樣的方式也可以實(shí)現(xiàn)。

對(duì)于Windows 運(yùn)行時(shí)類,實(shí)現(xiàn)IInspectable會(huì)增加額外的復(fù)雜度。這個(gè)接口并非是和IUnknown一樣的基礎(chǔ)性接口,它提供了一些不確定的工具的集合,而IUnknown則提供了絕對(duì)基礎(chǔ)的函數(shù)。關(guān)于此,我會(huì)為以后的文章留下一個(gè)討論,并聚焦于支持任意Windows運(yùn)行時(shí)類的高效和現(xiàn)代的C++實(shí)現(xiàn)。首先,我會(huì)避開GetRuntimeClassName和GetTrustLevel虛函數(shù)。實(shí)現(xiàn)這兩個(gè)方法相對(duì)而言微不足道,同時(shí)由于極少使用,所以它們的實(shí)現(xiàn)可以簡單搪塞一下。GetRunTimeClassName方法,需要返回這個(gè)對(duì)象所代表的運(yùn)行時(shí)類的完整名字的字符串。我將把這留給類自身去完成,決定是否去這樣做。實(shí)現(xiàn)類模板可以簡單地返回E_NOTIMPL,用來表明此方法并未實(shí)現(xiàn):

HRESULT __stdcall GetRuntimeClassName(HSTRING * name) noexcept  {    *name = nullptr;    return E_NOTIMPL;  }

同樣的,GetTrustLevel 方法也會(huì)簡單返回枚舉類型的常量:

HRESULT __stdcall GetTrustLevel(TrustLevel * trustLevel) noexcept  {    *trustLevel = BaseTrust;    return S_OK;  }

需要注意的是,我并沒有顯示的標(biāo)記這些IInspectable方法為虛函數(shù)。避免聲明為虛函數(shù),是為了讓編譯器剔除這些函數(shù),COM類無需真正的實(shí)現(xiàn)任何 IInspectable 接口?,F(xiàn)在,我將注意力轉(zhuǎn)移到IInspectable GetIids 方法。這比 QueryInterface 更容易產(chǎn)生錯(cuò)誤。盡管它的實(shí)現(xiàn)不是那么嚴(yán)格,但是一個(gè)高效的編譯器生成的實(shí)現(xiàn)也是可取的。GetIids 返回一個(gè)動(dòng)態(tài)分配的 GUID 數(shù)組。每個(gè) GUID 代表一個(gè)對(duì)象要實(shí)現(xiàn)的接口。起初你可能會(huì)認(rèn)為,這只是對(duì)象通過 QueryInterface 所支持的一個(gè)簡單的聲明,但是那只在表面上看是正確的。GetIids 方法可能會(huì)保留一些接口而不公開。不管怎樣,我會(huì)以基本定義作為開始:

HRESULT __stdcall GetIids(unsigned long * count,     GUID ** array) noexcept  {    *count = 0;    *array = nullptr;

***個(gè)參數(shù)指向調(diào)用者提供的變量,其中 GetIids 方法必須設(shè)置它為結(jié)果數(shù)組中的接口數(shù)目。第二個(gè)參數(shù)指向一個(gè) GUID 數(shù)組,同時(shí)也表示著這里的實(shí)現(xiàn)是如何將動(dòng)態(tài)分配的數(shù)組返回給調(diào)用者的。此處,我清除了這兩者參數(shù),只是為了安全起見?,F(xiàn)在我需要決定這個(gè)類實(shí)現(xiàn)多少個(gè)接口。我想說的是,使用 sizeof 操作符,它可以確定這個(gè)參數(shù)包的大小,如下:

unsigned const size = sizeof ... (Interfaces);

這相當(dāng)方便,同時(shí),編譯器也會(huì)報(bào)告參數(shù)包拓展后要展現(xiàn)的模板參數(shù)的數(shù)目。這也是一個(gè)有效的常量表達(dá)式,它會(huì)在編譯時(shí)產(chǎn)生一個(gè)值。我之前略為提及的,這無法實(shí)現(xiàn)的原因是,GetIids的實(shí)現(xiàn)會(huì)保留一些他們不愿和其他人共享的接口,這相當(dāng)普遍。這些接口被稱為隱含接口。任何人都可以通過QueryInterface來查詢它們,但是GetIids不會(huì)告知這些接口是可用的。這樣,我需要為排除了隱含接口的可變sizeof操作符,提供一個(gè)編譯時(shí)的替代品。同時(shí),我需要提供某種方式來聲明和標(biāo)識(shí)這些隱含接口。我以后者作為開始。對(duì)于組件開發(fā)人員,我希望讓實(shí)現(xiàn)類變得盡可能簡單,這樣就需要一個(gè)不太引人注意的技巧。我簡單的提供一個(gè)Cloaked類模板,用來“修飾”任意的隱含接口:

template <typename Interface>  struct Cloaked : Interface {};

然后,我決定在類Hen上實(shí)現(xiàn)一個(gè)特別的"IHenNative"接口,并非所有的使用者都知道它的存在:

class Hen : public Implements<IHen, IHen2, Cloaked<IHenNative>>  {  };

由于Cloaked類模板繼承自它的模板參數(shù),所以已存的QueryInterface實(shí)現(xiàn)可以繼續(xù)無縫工作。我已經(jīng)增加了一點(diǎn)額外的編譯時(shí)的類型信息,現(xiàn)在我可以查詢它們。由此,我會(huì)定義一個(gè)IsCloaked類型,這樣,我就可以很容易的查詢?nèi)我饨涌谝耘袛嗨欠癖浑[藏:

template <typename Interface>  struct IsCloaked : std::false_type {};  template <typename Interface>  struct IsCloaked<Cloaked<Interface>> : std::true_type {};

現(xiàn)在,我可以使用一個(gè)遞歸的可變函數(shù)模板,來計(jì)算未隱藏的接口數(shù)目:

template <typename First, typename ... Rest>  constexpr unsigned CounInterfaces() noexcept  {    return !IsCloaked<First>::value + CounInterfaces<Rest ...>();  }

當(dāng)然,我需要一個(gè)終止函數(shù),可以簡單的返回0:

template <int = 0>  constexpr unsigned CounInterfaces() noexcept  {    return 0;  }

使用現(xiàn)代C++在編譯時(shí)進(jìn)行算術(shù)計(jì)算的強(qiáng)大能力令人目瞪口呆,同時(shí)也簡單的令人驚嘆?,F(xiàn)在我通過請(qǐng)求數(shù)量來繼續(xù)完善GetIids的實(shí)現(xiàn):

unsigned const localCount = CounInterfaces<Interfaces ...>();

一個(gè)不太圓滿的地方是,編譯器對(duì)常量表達(dá)式的支持還不是很成熟。盡管,這毫無疑問是一個(gè)常量表達(dá)式,但是編譯器卻不會(huì)如此看待constexpr成員函數(shù)。理想情況下,我可以標(biāo)識(shí)CountInterfaces函數(shù)模板為constexpr,同時(shí)結(jié)果表達(dá)式也將是一個(gè)常量表達(dá)式,但是,編譯器不會(huì)這認(rèn)為。另一方面,毋庸置疑,編譯器會(huì)優(yōu)化這段代碼?,F(xiàn)在,不管是什么原因,如果CountInterfaces沒有找到隱含接口,GetIids會(huì)簡單的返回成功,因?yàn)榻Y(jié)果數(shù)組會(huì)為空:

if (0 == localCount)  {    return S_OK;  }

同樣的,這也是一個(gè)有效的常量表達(dá)式,編譯器會(huì)無需任何條件的生成代碼。換句話說,如果沒有未隱藏的接口,剩下的代碼會(huì)簡單的從實(shí)現(xiàn)中刪除。否則,代碼的實(shí)現(xiàn),會(huì)強(qiáng)制要求使用傳統(tǒng)的COM分配器,分配一個(gè)合理大小的GUID數(shù)組:

GUID * localArray = static_cast<GUID *>(CoTaskMemAlloc(sizeof(GUID) * localCount));

當(dāng)然,這可能失敗,在這種情況下,我簡單的返回合適的HRESULT值:

if (nullptr == localArray)  {    return E_OUTOFMEMORY;  }

在這點(diǎn)上,GetIids準(zhǔn)備好一個(gè)數(shù)組用來存放GUID。就像你所期待的那樣,我需要***一次枚舉接口,然后拷貝每個(gè)未隱藏的接口的GUID到這個(gè)數(shù)組之中。我將使用一組函數(shù)模板,就像我之前使用的那樣:

template <int = 0>  void CopyInterfaces(GUID *) noexcept {}  template <typename First, typename ... Rest>  void CopyInterfaces(GUID * ids) noexcept  {  }

這個(gè)可變模板(第二個(gè)函數(shù))可以簡單的使用IsCloaked類型來決定,在增加指針之前,是否拷貝由First模板參數(shù)標(biāo)識(shí)的接口的GUID。使用這種方式,可以遍歷數(shù)組,而無需記錄它包含多少個(gè)元素,或者它將寫入數(shù)組的哪個(gè)位置。我也禁止了關(guān)于常量表達(dá)式的警告:

#pragma warning(push)  #pragma warning(disable:4127) // Conditional expression is constant  if (!IsCloaked<First>::value)  {    *ids++ = __uuidof(First);  }  #pragma warning(pop)  CopyInterfaces<Rest ...>(ids);

如你所見,在***“遞歸”調(diào)用CopyInterfaces使用了可能增加的指針的值。我?guī)缀跬瓿闪?整個(gè)實(shí)現(xiàn)過程)。在返回給調(diào)用者之前,GetIids的實(shí)現(xiàn)可以通過調(diào)用CopyInterfaces來填充數(shù)組:

CopyInterfaces<Interfaces ...>(localArray);    *count = localCount;    *array = localArray;    return S_OK;  }

對(duì)于Hen類來說,編譯器在它上面的所有操作都是透明的(它完全不知道這些操作):

class Hen : public Implements<IHen, IHen2, Cloaked<IHenNative>>  {  };

感謝各位的閱讀,以上就是“Visual C++ 2015引入更新的C++特性有哪些”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)Visual C++ 2015引入更新的C++特性有哪些這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

c++
AI