Visual C++ 2015 引入更新的 C++ 特性到 Windows API

oschina發表於2015-02-03

  Visual C++ 2015 是 C++ 團隊付出巨大努力將現代C++引入windows平臺的成果。在最新的幾個發行版本里,VC++已經逐步新增了現代C++語言以及庫的特色,這些結合在一起會創造一個用於構建通用windows App和元件的絕對驚豔的開發環境。Visual C++2015建立在早期版本引入的驚人進步,提供了成熟的、支援大多數C++11特性以及C++ 2015子集的編譯器。你或許會懷疑編譯器支援的完整程度,公正地說,我認為他能支援大部分重要的語言特性,支援現代C++將會迎來windows 程式庫開發一片新的天地。這才是關鍵。只要編譯器支援一個高效優雅的庫的開發環境,開發者就能構建偉大的app和元件。

  這裡我不會讓你看一個枯燥的新特性列表,或者走馬觀花地看下它的功能,而是會帶你瀏覽下一些傳統情況下的複雜程式碼現在如何讓人相當愉快書寫。當然,這得益於成熟的Visual C++編譯器。我將會向你展示windows的一些本質,在現在或將來API中實際上都是很重要的本質。

  頗具諷刺意味的是,對於COM來說,C++已經足夠現代了.是的,我在談論元件物件模型(COM),多年以來,它一直是大多數Windows API的基石.同時,它也繼續作為Windows執行時的基石.COM無可爭辯的依附於C++的原始設計,借鑑了許多來自C++的二進位制和語義約定,但是它從來都不夠優雅.C++的部分內容被認為可移植性不夠,如dynamic_cast,必須避免使用它,以採用可移植的解決方案,這使得C++的開發實現更具挑戰性.近些年已經為C++開發者提供了許多解決方案,讓COM變得更加可移植.C++/CX 語言擴充,可能是Visual C++團隊到目前為止最具野心的.具有諷刺意味的是,這些提升標準C++支援的努力,已經將C++/CX棄之不顧了,也讓語言擴充變得冗餘.

  為了證明這點,我會展示給你如何完整的用現代C++實現IUnknown和IInspectable介面.關於這兩個介面沒有什麼現代的或吸引力的東西.IUnknown繼續成為卓越API,如DirectX,的集中抽象.這些介面--IInspectable繼承自IUnknown--位於Windows執行時的中心.我將展示給你如何不用任何語言擴充來實現它們,介面表或其它巨集--只需要包含大量型別資訊的高效和優雅的C++,就可以讓編譯器和開發者擁有,關於如何建立所需的,優異的人機對話.

  主要的問題是, 如何列出  COM 或 Windows Runtime 類需要實現的介面, 而且要方便開發者使用, 和編譯器訪問. 比如, 列出所有可用型別, 以便編譯器查詢, 甚至列舉出相應的介面. 要是能實現這樣的功能, 也許就能讓編譯器生成 IUnknown QueryInterface 甚至 IInspectable GetIids 方法的程式碼. 這兩個方法才是問題的關鍵. 按照傳統的觀念, 唯一的解決辦法涉及到語言擴充套件(language extensions), 萬惡的巨集定義, 以及一堆難以維護的程式碼.

  兩種方法的實現, 都用到類需要實現的介面. 可變引數模板( variadic template)是首選:

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

  __declspec(novtable)擴充屬性可以防止建構函式和解構函式初始化抽象類的vfptr,這通常意味著減少大量的程式碼.實現類别範本包括一個模板引數包,這使它成為一個可變模板.一個引數包即一個模板引數接受任意數目的模板引數變數.但是在這種情況下,我描述的模板引數將只會在編譯時進行查詢.介面將不會出現在函式的引數列表之中.

  這些引數的一個使用已經顯而易見.引數包擴充後成為公共基礎類的引數列表.當然,我仍然有責任到最後實現這些虛擬函式,但是此刻我會描述一個實現任意數目介面的一個具體類:

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

  因為引數包擴充為指定基礎類的列表,所有它等同於下面我可能會寫出的程式碼:

class Hen : public IHen, public IHen2
{
};

  用這種方式結構化實現類别範本的美妙之處在於,我現在可以,在實現類别範本中,寫入各種樣版實現程式碼,而Hen類的開發者則可以使用這種不唐突的抽象,同時大量忽略隱含的細節.

  到目前為止,一切都很好.現在,我將考慮IUnknown的實現.我應該可以在實現類别範本中完整的實現它,並提供編譯器現在所擁有的型別資訊.IUnknown提供了對於COM類非常重要的兩種工具,就像氧氣和水對於人類一樣.第一個可能簡單些的是引用計數,這也是COM物件跟蹤它們生命週期的方式.COM規定一種侵入式的引用計數,它藉助於每個物件,統計多少個外部引用存在,來負責管理自己的生命週期.這與智慧指標,如C++ 11的shared_ptr類,的引用計數恰恰相反,智慧指標物件並不知道它的共享關係.你可能會爭論這兩種方式的優缺點.但是,實際上COM的方法通常更高效,這也是COM的工作方式,你必須處理它.如果沒有其它的,你很可能會同意這點,在shared_ptr裡面包裝一個COM介面會是一件極不友好的事情!

  我將以只有執行時的開銷作為開始,它是通過實現類别範本介紹的:

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

  預設建構函式並不是真正的開銷所在,它只是簡單的確保最終的建構函式--它將初始化引用計數--為protected而不是public的.引用計數和虛建構函式都是protected的.讓派生類訪問引用計數,是為了允許更復雜的類組合.大多數類可以簡單的忽略它,但是需要注意的是,我正初始化引用計數為1.這和通常建議初始化引用計數為0,形成鮮明的對比,因為此時並沒有處理引用.這個方式在ATL中非常流行,明顯受到Don Box的COM本質論的影響,但是這是非常有問題的,ATL的原始碼的研究可以作為佐證.開始於這個假設,即引用的所有權將會立即由呼叫者獲得,或者依附於一個提供更少錯誤構造處理的智慧指標.

  虛解構函式提供了很大的便利性,它允許實現類别範本實現引用計數,而不是強制實現類本身來提供實現.另一個選項,是使用奇特的遞迴模板模式(Curiously Recurring Template Pattern)來避免使用虛擬函式.通常我會選擇這個方法,但是它會稍微增加抽象的複雜性,同時,因為COM類本身有一個vtable,所以這裡也沒有什麼理由去避免使用虛擬函式.有了這些基本型別之後,在實現類别範本中實現AddRef和Release將會變得非常簡單.首先,AddRef方法可以簡單的使用InterlockedIncrement來增加引用計數:

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

  這不言自明.不要想出某些複雜的方法,通過使用C++的加減操作符來有條件的替換InterlockedIncrement和InterlockedDecrement函式.ATL通過極大的增加複雜性去做這個嘗試.如果你考慮效率,寧可為避免呼叫AddRef和Release產生謬誤而多花心思.同樣的,現代C++增加了對move語義的支援,以及增加轉移引用所有權的能力.現在,Release方法只是略顯複雜:

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

  引用計數減少後,結果被賦值給臨時變數.這很重要,因為結果需要返回.但是如果物件銷燬了,引用此物件的成員變數就是非法的了.假定沒有其它未處理的引用,這個物件就通過前面說到的虛解構函式刪除了.這就是引用計數的結論,實現類Hen仍然和之前的一樣簡單:

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

  現在,到了想象一下QueryInterface的奇妙世界的時間了。實現IUnknown方法是一個很重要的實踐。在我的Pluralsight課程中,我廣泛的實現了它。你可以在Don Box編寫的<<COM本質論>>(Addison-Wesley Professional,1998)一書中,閱讀關於實現你自己的IUnknown的奇妙的和不可思議的方法。需要注意的是,雖然這是一本關於COM的優秀書籍,但是它是基於C++98的,並沒有呈現出任何現代C++的特徵。為了節省時間,我假定你已經熟悉了QueryInterface的實現過程,並集中於如何用現代C++實現它。下面是虛擬函式本身:

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

  給定一個GUID用來標識一個特別的介面之後,QueryInterface應該來決定一個物件是否實現了需要的介面。如果實現了,它必須減少這個物件的引用計數,同時通過外部引數來返回所需的介面指標。如果沒有實現,它必須返回一個空指標。因此,我將以一個粗略的輪廓來作為開始:

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

  QueryInterface首先會嘗試設法查詢所需的介面。如果介面受不支援,則返回E_NOINTERFACE錯誤碼。請注意,我是如何按照要求處理介面指標不支援的情況。你應該把QueryInterface介面看作是二元的操作。它要麼成功找到所需的介面,要麼查詢失敗。不要嘗試發揮創造性,只需要依據條件響應即可。儘管COM規範有一些限制項,但是大多數消費者都會簡單的假定介面不受支援,而不管你會返回何種錯誤碼。在你的實現中的任何錯誤,都毫無疑問的會導致你陷入除錯的深淵。QueryInterface是非常基礎的,不能胡亂對待。最後,AddRef由介面指標再次呼叫,用來支援某種極少的而又允許的類組合場景。這些不受實現類别範本的顯式支援,但是我情願在這裡做一個表率。重要的是,記住引用計數操作是面向介面的,而不是物件導向的。你不能 簡單的,在屬於一個物件的任意介面上面,呼叫AddRef或者Release。你必須依賴COM規則來管理物件,否則你會冒險引入以不可思議的方式崩潰的非法程式碼。

  但是我如何得知,請求的GUID是否就代表著類想要實現的介面呢?我需要回到實現類别範本收集的型別資訊的地方,其中型別資訊通過它的模板引數包來收集。請記住,我的目標是准許編譯器為我實現它。我希望最終程式碼,和我手寫的一樣高效,甚至更好。我會通過可變函式模板集合來進行查詢,函式模板自身包括模板引數包。我將以BaseQueryInterface函式模板作為開始:

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

  BaseQueryInterface本質上是IUnknown QueryInterface的現代C++版本。它直接返回介面指標而不是HRESULT型別。空指標則表明失敗的情況。它接受單一函式引數,GUID標識著要查詢的介面。更重要的是,我擴充了類别範本引數包為完整模式,這樣,BaseQueryInterface函式就可以開始列舉介面的處理過程。 起初你可能會認為,由於BaseQueryInterface是實現類别範本的成員函式,所以它可以簡單直接的訪問介面的連結串列,但是我需要准許這個函式剝離連結串列中的第一個介面,就像下面這樣:

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

  按照這種方式,BaseQueryInterface函式能識別第一個介面,並且給接下來的搜尋留有餘地。看吧,COM有一定數量的特殊規則來支援QueryInterface 實現或至少接受物件識別。尤其是請求IUnknown,必須總是返回確切相同的指標,客戶端才能確定兩個介面的指標是否來自同一個物件。因此,BaseQueryInterface函式最棒的地方就是實現了這些假設。所以,可以從首個代表類想要實現的第一個介面的模板引數的GUID請求的對比開始。如果不匹配,我會檢查IUnknown是否開始請求了:

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

  假設有一個匹配的,我直接準確無誤的返回了第一個介面的指標。static_cast 能確保編譯器基於IUnknown的多種介面不會引起歧義。cast只是校準了指標,讓類的vtable能找到正確的指標位置,因為所有vtable介面是以IUnknown的三個方法開始的,這非常符合邏輯。

  而我不妨同樣新增IInspectable查詢的可選支援。IInspectable相當變態,在某種意義上,它是Windows執行時介面,因為每個Windows執行時預計程式語言(如 C# 和 JavaScript)必須直接來自IInspectable,而不僅僅只是IUnknown介面。相對於C++的工作方式和COM傳統的定義方式,以適應公共語言執行庫的方式實現物件和介面是不幸的事實。更不幸的是當物件組合的時候對效能的影響,我會在下文中討論。至於QueryInterface,我只需確保IInspectable能被查詢,它應該是一個Windows執行時類的實現,而不是一個簡單的典型COM類。雖然關於IUnknown的明確的COM規則不適用於IInspectable,我可以簡單的用相同的方式對待後者。但這兩個挑戰。首先,需要了解是否有任何IInspectable派生出來的介面實現。第二,需要了解介面的型別,這樣就可以正確的返回一個沒有歧義的調整過的介面指標。假定列表中的第一個介面都是基於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 的特性,來確定第一個模板引數是一個IInspectable的衍生介面。萬一實現典型的COM類不支援Windows執行時,就能確保隨後的對照是由編譯器排除的。這樣我可以無縫地支援Windows執行時和經典的COM類,即沒有增加元件開發人員的語句複雜性,也沒有任何不必要的執行時開銷。但是,如果恰好遇列舉出來得首位不是IInspectable介面,就會有不容易察覺的Bug的隱患。所需要做的就是,用某種方法替代is_base_of來掃描整個介面的列表:

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

  IsInspectable 也是基於is_base_of特性的,但是當前適用於匹配介面。如果沒找到基於IInspectable 的介面則終止:

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

  我會禁用掉稀奇古怪的預設引數。假定 IsInspectable 返回的是 true,我需要找到第一個IInspectable-based 介面:

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

  再次使用 is_base_of 特性,但這次要返回一個真實匹配的介面指標:

#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 這時可以利用IsInspectable 和 FindInspectable 一起來支援查詢 IInspectable:

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

  然後指定具體的 Hen 類:

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

  實現類的模板,可以確保編譯器能生成更高效的程式碼,不管 IHen、Hen2 來自 IInspectable 還是 IIUnknown (或者其他介面)。現在,我可以最後實現 QueryInterface 的遞迴部分,以及任何追加的介面,例如上面例子中的 IHen2。BaseQueryInterface 是靠呼叫 FindInterface 函式模板結束的: 

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);
}

  注意,我呼叫這個FindInterface函式模板,大致等同於我原來呼叫的BaseQueryInterface,在這個例子中,我向它傳遞介面的其餘部分。我特意再次擴大引數包,這樣它可以在列表的其餘部分識別第一介面。但會提示一個故障。由於模板引數包不是以函式實參來擴充套件的,這可能會變得棘手,程式語言寫不出來我想要的。更多的時候,這種“遞迴的”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);
}

  它會從模板引數的其餘部分中分離,如果有匹配就返回撥整過的介面指標。另外,它也會呼叫自己,直到list取完。當我籠統地提及編譯期遞迴時,重要的是要注意這個函式模板,以及其他類似的實現類别範本的例子,在技術上遞迴,而不是在編譯期。每個函式模板的例項呼叫不同的函式模板的例項。例如,FindInterface<IHen, IHen2> 呼叫 FindInterface<IHen2>, FindInterface<IHen2>呼叫 FindInterface<>。為了讓它遞迴, FindInterface<IHen, IHen2>不需要呼叫FindInterface<IHen, IHen2>。

  儘管如此,還是要記住,這樣的“遞迴”發生在編譯時,它就像你自己手寫的一條條if語句。但是,現在,我遇到麻煩了。這個序列如何終止呢?當然是當模板引數列表為空的時候。這個問題在於C++已經定義了空模板引數列表的含義:

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

  這幾乎是正確的,但是編譯器會提示你,函式模板在這個特化中無法使用。同時,如果我不提供終止函式,當引數包為空的時候,編譯器將無法編譯最終的呼叫。這不是函式過載的情況,因為引數列表依舊是相同的。幸運的是,解決方案非常簡單。我可以通過提供一個無名的預設引數,來避免終止函式看起來像一個特化:

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

  編譯器樂於此,同時,如果請求一個不支援的介面,終止函式會簡單的返回一個空指標,同時虛擬函式QueryInterface將返回E_NOINTERFACE錯誤碼。對IUnknown而言,這考慮得很周到。如果你所關心的是經典的COM,你可以安全的停在那裡,因為那就是你所需要的所有內容。關於這點,可以反覆的操作,編譯器將優化QueryInterface的實現,其間使用各種各樣的“遞迴的”函式呼叫和常量表示式,程式碼至少和你手工寫的一樣好。對於IInspectable而言,同樣的方式也可以實現。

  對於Windows 執行時類,實現IInspectable會增加額外的複雜度。這個介面並非是和IUnknown一樣的基礎性介面,它提供了一些不確定的工具的集合,而IUnknown則提供了絕對基礎的函式。關於此,我會為以後的文章留下一個討論,並聚焦於支援任意Windows執行時類的高效和現代的C++實現。首先,我會避開GetRuntimeClassName和GetTrustLevel虛擬函式。實現這兩個方法相對而言微不足道,同時由於極少使用,所以它們的實現可以簡單搪塞一下。GetRunTimeClassName方法,需要返回這個物件所代表的執行時類的完整名字的字串。我將把這留給類自身去完成,決定是否去這樣做。實現類别範本可以簡單地返回E_NOTIMPL,用來表明此方法並未實現:

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

  同樣的,GetTrustLevel 方法也會簡單返回列舉型別的常量:

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

  需要注意的是,我並沒有顯示的標記這些IInspectable方法為虛擬函式。避免宣告為虛擬函式,是為了讓編譯器剔除這些函式,COM類無需真正的實現任何 IInspectable 介面。現在,我將注意力轉移到IInspectable GetIids 方法。這比 QueryInterface 更容易產生錯誤。儘管它的實現不是那麼嚴格,但是一個高效的編譯器生成的實現也是可取的。GetIids 返回一個動態分配的 GUID 陣列。每個 GUID 代表一個物件要實現的介面。起初你可能會認為,這只是物件通過 QueryInterface 所支援的一個簡單的宣告,但是那隻在表面上看是正確的。GetIids 方法可能會保留一些介面而不公開。不管怎樣,我會以基本定義作為開始:

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

  第一個引數指向呼叫者提供的變數,其中 GetIids 方法必須設定它為結果陣列中的介面數目。第二個引數指向一個 GUID 陣列,同時也表示著這裡的實現是如何將動態分配的陣列返回給呼叫者的。此處,我清除了這兩者引數,只是為了安全起見。現在我需要決定這個類實現多少個介面。我想說的是,使用 sizeof 操作符,它可以確定這個引數包的大小,如下:

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

  這相當方便,同時,編譯器也會報告引數包擴充後要展現的模板引數的數目。這也是一個有效的常量表示式,它會在編譯時產生一個值。我之前略為提及的,這無法實現的原因是,GetIids的實現會保留一些他們不願和其他人共享的介面,這相當普遍。這些介面被稱為隱含介面。任何人都可以通過QueryInterface來查詢它們,但是GetIids不會告知這些介面是可用的。這樣,我需要為排除了隱含介面的可變sizeof操作符,提供一個編譯時的替代品。同時,我需要提供某種方式來宣告和標識這些隱含介面。我以後者作為開始。對於元件開發人員,我希望讓實現類變得儘可能簡單,這樣就需要一個不太引人注意的技巧。我簡單的提供一個Cloaked類别範本,用來“修飾”任意的隱含介面:

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

  然後,我決定在類Hen上實現一個特別的"IHenNative"介面,並非所有的使用者都知道它的存在:

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

  由於Cloaked類别範本繼承自它的模板引數,所以已存的QueryInterface實現可以繼續無縫工作。我已經增加了一點額外的編譯時的型別資訊,現在我可以查詢它們。由此,我會定義一個IsCloaked型別,這樣,我就可以很容易的查詢任意介面以判斷它是否被隱藏:

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

  現在,我可以使用一個遞迴的可變函式模板,來計算未隱藏的介面數目:

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

  當然,我需要一個終止函式,可以簡單的返回0:

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

  使用現代C++在編譯時進行算術計算的強大能力令人目瞪口呆,同時也簡單的令人驚歎。現在我通過請求數量來繼續完善GetIids的實現:

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

  一個不太圓滿的地方是,編譯器對常量表示式的支援還不是很成熟。儘管,這毫無疑問是一個常量表示式,但是編譯器卻不會如此看待constexpr成員函式。理想情況下,我可以標識CountInterfaces函式模板為constexpr,同時結果表示式也將是一個常量表示式,但是,編譯器不會這認為。另一方面,毋庸置疑,編譯器會優化這段程式碼。現在,不管是什麼原因,如果CountInterfaces沒有找到隱含介面,GetIids會簡單的返回成功,因為結果陣列會為空:

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

  同樣的,這也是一個有效的常量表示式,編譯器會無需任何條件的生成程式碼。換句話說,如果沒有未隱藏的介面,剩下的程式碼會簡單的從實現中刪除。否則,程式碼的實現,會強制要求使用傳統的COM分配器,分配一個合理大小的GUID陣列:

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

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

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

  在這點上,GetIids準備好一個陣列用來存放GUID。就像你所期待的那樣,我需要最後一次列舉介面,然後拷貝每個未隱藏的介面的GUID到這個陣列之中。我將使用一組函式模板,就像我之前使用的那樣:

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

  這個可變模板(第二個函式)可以簡單的使用IsCloaked型別來決定,在增加指標之前,是否拷貝由First模板引數標識的介面的GUID。使用這種方式,可以遍歷陣列,而無需記錄它包含多少個元素,或者它將寫入陣列的哪個位置。我也禁止了關於常量表示式的警告:

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

  如你所見,在最後“遞迴”呼叫CopyInterfaces使用了可能增加的指標的值。我幾乎完成了(整個實現過程)。在返回給呼叫者之前,GetIids的實現可以通過呼叫CopyInterfaces來填充陣列:

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

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

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

  這就是一個好的庫所應該有的樣子。Visual C++ 2015編譯器提供了Windows平臺下面對於標準C++的完美支援。它允許C++開發者建立優雅而高效的庫。這同樣支援使用標準C++開發Windows執行時元件,以及完全使用標準C++編寫的通用的Windows應用程式。實現類别範本僅僅只是現代C++針對Windows執行時的一個例子。(檢視 moderncpp.com).

  Kenny Kerr 是一位加拿大的開發者,同樣也是Pluralsight的作者,以及微軟的MVP。 它的部落格是 kennykerr.ca。你可以關注他的Twitter賬號twitter.com/kennykerr

  多謝微軟的技術專家James McNellis審閱這篇文章。

  原文地址:http://msdn.microsoft.com/en-us/magazine/dn879357.aspx

相關文章