共用方式為


診斷直接分配

使用 C++/WinRT撰寫 API 中所述,當您建立實作類型的物件時,應該使用 winrt::make 系列協助程式來執行此動作。 本主題深入探討 C++/WinRT 2.0 功能,協助您診斷在堆疊上直接分配實作型別物件的錯誤。

這類錯誤可能會變成神秘的當機或損毀,難以偵錯且耗時。 因此,這是一個重要功能,值得瞭解背景。

使用 MyStringable 設定場景

我們來看 IStringable的簡單實現。

struct MyStringable : implements<MyStringable, IStringable>
{
    winrt::hstring ToString() const { return L"MyStringable"; }
};

現在假設您需要調用一個函數,從您的實作中,這個函數需要一個 IStringable 作為參數。

void Print(IStringable const& stringable)
{
    printf("%ls\n", stringable.ToString().c_str());
}

問題在於,我們的 MyStringable 類型 並不是IStringable

  • 我們的 MyStringable 類型是 IStringable 介面的實作。
  • IStringable 類型是預期類型。

這很重要

請務必瞭解 實作類型投影類型之間的差異。 如需了解基本概念和術語,請務必閱讀 用 C++/WinRT 消費 API用 C++/WinRT 開發 API

實作與投影之間的空間細微而難以理解。 事實上,為了讓實作感覺更像是投影,實作會提供隱含轉換給它實作的每個投影類型。 這並不意味著我們可以簡單地這樣做。

struct MyStringable : implements<MyStringable, IStringable>
{
    winrt::hstring ToString() const;
 
    void Call()
    {
        Print(this);
    }
};

相反地,我們需要取得參考,以便轉換運算符可用來作為解析呼叫的候選專案。

void Call()
{
    Print(*this);
}

這樣可以。 隱含轉換提供從實作類型到投影類型的轉換(非常有效率),這在許多案例中都很方便。 如果沒有該設施,許多實作類型會證明非常繁瑣。 假設您只使用 winrt::make 函式範本 (或 winrt::make_self)來配置實作,則一切順利。

IStringable stringable{ winrt::make<MyStringable>() };

C++/WinRT 1.0 的潛在陷阱

不過,隱含轉換可能會讓您陷入困境。 請考慮這個無幫助的協助程式函式。

IStringable MakeStringable()
{
    return MyStringable(); // Incorrect.
}

或者甚至只是這句看似無害的話。

IStringable stringable{ MyStringable() }; // Also incorrect.

不幸的是,這類程式代碼 確實 編譯C++/WinRT 1.0,因為該隱含轉換。 我們面臨的一個(非常嚴重)問題是,我們可能會傳回一個指向參考計數物件的預測類型,而該物件的底層記憶體位於暫時堆疊上。

以下是以 C++/WinRT 1.0 編譯的其他專案。

MyStringable* stringable{ new MyStringable() }; // Very inadvisable.

原始指標是危險且需要大量人力的 Bug 來源。 如果您不需要的話,請勿使用它們。 C++/WinRT 會盡其所能,讓一切更有效率,而不需要強迫您使用原始指標。 以下是以 C++/WinRT 1.0 編譯的其他專案。

auto stringable{ std::make_shared<MyStringable>(); } // Also very inadvisable.

這是幾個層級的錯誤。 我們針對同一個物件有兩個不同的參考計數。 Windows 運行時(以及其之前的傳統 COM)基於一種與 std::shared_ptr不相容的內部參考計數。 當然,std::shared_ptr 有很多有效的用途;但是在共用 Windows 執行階段 (和傳統 COM)物件時,這是完全沒有必要的。 最後,這也會使用 C++/WinRT 1.0 編譯。

auto stringable{ std::make_unique<MyStringable>() }; // Highly dubious.

這再次相當可疑。 唯一擁有權與 MyStringable的內部參考計數的共享存活期相對立。

C++/WinRT 2.0 的解決方案

使用 C++/WinRT 2.0 時,所有這些嘗試直接配置實作類型都會導致編譯程序錯誤。 這是最好的錯誤類型,而且比神秘的執行期錯誤更好。

每當您需要進行實作時,只要使用 的 winrt::make的 winrt::make_self,即可,如上所示。 現在,如果您忘了這麼做,您將會突然收到一個編譯器錯誤,該錯誤暗示此問題並引用了一個名為 use_make_function_to_create_this_object的抽象函式。 它不是完全 static_assert,但很接近。 不過,這是偵測所有所描述錯誤的最可靠方式。

這確實表示我們需要對實作放置一些次要條件約束。 由於我們依賴缺少覆寫來偵測直接配置的情況,winrt::make 函式範本必須設法以覆寫來滿足抽象虛擬函式。 其方式是從提供覆寫的 final 類別衍生出來進行實作。 有一些關於此程序的觀察事項。

首先,虛擬函式只會出現在偵錯組建中。 這表示偵測不會影響優化組建中 vtable 的大小。

其次,由於 winrt::make 使用的衍生類別是 final,這表示即使您之前選擇不將實作類別標示為 final,優化器仍可能推斷並進行去虛擬化。 所以這是一個改進。 相反地,您的實作 不能final。 同樣地,這不會造成任何後果,因為實例化類型一律會是 final

第三,沒有任何專案會阻止您將實作中的任何虛擬函式標示為 final。 當然,C++/WinRT 與傳統 COM 和 WRL 之類的實作非常不同,其中有關實作的所有專案通常都是虛擬的。 在 C++/WinRT 中,虛擬分派僅限於應用程式二進位介面 (ABI)(一律 final),而您的實作方法依賴編譯時間或靜態多型。 這可避免不必要的執行期間多型,也表示在您的 C++/WinRT 實作中幾乎沒有虛擬函式的必要性。 這是一件非常好的事情,這樣可以使內嵌更加可預測。

第四,由於 winrt::make 插入衍生類別,因此您的實作不能有私用解構函式。 私有解構函式在傳統的 COM 實作中很受歡迎,這是因為所有東西都是虛擬的,通常會直接處理原始指標,因此很容易不小心呼叫 delete 而不是 Release。 C++/WinRT 特意設計為讓您難以直接處理原始指標。 你必須 真正 走出去,在C++/WinRT 中取得原始指標,您可能會呼叫 delete。 值語意意味著您正在處理值和參照,而很少與指標。

因此,C++/WinRT 挑戰我們先入為主的概念,即撰寫傳統 COM 程式代碼的意義。 這完全合理,因為 WinRT 不是傳統 COM。 傳統 COM 是 Windows 執行階段的組合語言。 它不應該是您每天撰寫的程序代碼。 相反地,C++/WinRT 可讓您撰寫更像是新式C++的程序代碼,而且更不像傳統 COM。

重要 API