C++/WinRT を使用した Author 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 型は投影型です。
Von Bedeutung
実装型と投影型の違いを理解することが重要です。 基本的な概念と用語については、「 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.
生のポインターは、危険で手間のかかるバグの原因です。 必要がない場合は使用しないでください。 C++/WinRT は、生ポインターの使用をいっさい強制することなく、あらゆる処理を効率的に行えるよう徹底的に設計されています。 C++/WinRT 1.0 でコンパイルされた他の内容を次に示します。
auto stringable{ std::make_shared<MyStringable>(); } // Also very inadvisable.
これはいくつかのレベルの間違いです。 同じオブジェクトに対して 2 つの異なる参照カウントがあります。 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 のサイズに影響しません。
2 つ目は、 winrt::make が使用する派生クラスが finalであるため、以前に実装クラスを final としてマークしなかった場合でも、オプティマイザーが推測できる可能性のある非仮想化が発生することを意味します。 だから、それは改善です。 逆に、実装をfinal。 ここでも、インスタンス化された型は常に finalされるため、これは何の影響もありません。
3 つ目は、実装内の仮想関数を finalとしてマークすることを妨げるものはありません。 もちろん、C++/WinRT は従来の COM や WRL などの実装とは大きく異なり、実装に関するすべてのものが仮想である傾向があります。 C++/WinRT では、仮想ディスパッチはアプリケーション バイナリ インターフェイス (ABI) (常に final) に制限され、実装メソッドはコンパイル時または静的ポリモーフィズムに依存します。 これにより、不要なランタイムポリモーフィズムが回避され、C++/WinRT 実装に仮想関数の貴重な理由がほとんど存在しないことを意味します。 これは非常に良いことであり、はるかに予測可能なインライン化につながります。
4 つ目は、 winrt::make によって派生クラスが挿入されるため、実装にプライベートデストラクターを含めることはできません。 プライベート デストラクターは従来の COM 実装で人気がありました。これは、やはりすべてが仮想的であり、生のポインターを直接処理するのが一般的であり、delete ではなく誤ってを呼び出しやすいためです。 C++/WinRT では、生のポインターを直接処理するのが難しくなります。 また、deleteを呼び出しかねないような C++/WinRT の生ポインターを取得するには、本当にわざわざそうしなければなりません。 値セマンティクスとは、値と参照を処理していることを意味します。まれにポインターを使用します。
そのため、C++/WinRT では、従来の COM コードを記述する意味について先入観に挑戦しています。 WinRT は従来の COM ではないため、これは完全に合理的です。 クラシック COM は、Windows ランタイムのアセンブリ言語です。 毎日記述するコードではないはずです。 代わりに、C++/WinRT を使用すると、最新の C++ に似た、従来の COM に比べてはるかに少ないコードを記述できます。
重要な API
関連トピック
Windows developer