次の方法で共有


直接割当ての診断

C++/WinRTAuthor 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としてマークすることを妨げるものは何もありません。 もちろん、C++/WinRT は従来の COM や WRL などの実装とは大きく異なり、実装に関するすべてのものが仮想である傾向があります。 C++/WinRT では、仮想ディスパッチはアプリケーション バイナリ インターフェイス (ABI) (常に final) に制限され、実装メソッドはコンパイル時または静的ポリモーフィズムに依存します。 これにより、不要なランタイムポリモーフィズムが回避され、C++/WinRT 実装に仮想関数の貴重な理由がほとんど存在しないことを意味します。 これは非常に良いことであり、はるかに予測可能なインライン化につながります。

4 番目に、winrt::make が派生クラスを挿入するので、実装にプライベート デストラクターを持てません。 従来のCOM実装では、すべてが仮想的であり、生のポインターを直接扱うことが一般的だったため、プライベートデストラクターが人気でした。これにより、deleteの代わりに誤って を呼び出してしまうことが容易でした。 C++/WinRT では、生のポインターを直接処理するのが難しくなります。 C++/WinRT で を呼び出せるような生ポインターを手に入れるには、delete 手間をかける必要があります。 値セマンティクスとは、値と参照を処理していることを意味します。まれにポインターを使用します。

そのため、C++/WinRT では、従来の COM コードを記述する意味について先入観に挑戦しています。 WinRT は従来の COM ではないため、これは完全に合理的です。 クラシック COM は、Windows ランタイムのアセンブリ言語です。 毎日記述するコードではないはずです。 代わりに、C++/WinRT を使用すると、最新の C++ に似た、従来の COM に比べてはるかに少ないコードを記述できます。

重要な API