Doğrudan ayırmaları teşhis etme

C++/WinRT ile Yazma API'lerinde açıklandığı gibi, uygulama türüne sahip bir nesne oluşturduğunuzda, bunu yapmak için winrt::make yardımcı ailesini kullanmanız gerekir. Bu konu, yığında uygulama türündeki bir nesneyi doğrudan ayırma hatasını tanılamanıza yardımcı olan bir C++/WinRT 2.0 özelliğinde ayrıntılı olarak açıklanmaktadır.

Bu tür hatalar, hata ayıklanması zor ve zaman alıcı olan, nedeni belirsiz kilitlenmelere veya bozulmalara yol açabilir. Bu nedenle bu önemli bir özelliktir ve arka planı anlamaya değer.

MyStringable ile sahneyi ayarlama

İlk olarak, IStringable'ın basit bir uygulamasını ele alalım.

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

Şimdi bir IStringable'ı bağımsız değişken olarak bekleyen bir işlevi (uygulamanızın içinden) çağırmanız gerektiğini düşünün.

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

Sorun, MyStringable türümüzün birIStringableolmamasıdır.

  • MyStringable türümüz, IStringable arabiriminin bir uygulamasıdır.
  • IStringable türü, yansıtılmış bir türdür.

Önemli

Uygulama türü ile öngörülen tür arasındaki ayrımı anlamak önemlidir. Temel kavramlar ve terimler için C++/WinRT ile API'leri kullanma ve C++/WinRTile YAZMA API'lerini okuduğunuzdan emin olun.

Bir uygulama ile projeksiyon arasındaki boşluğu kavramak zor olabilir. Ve aslında, gerçekleştirimin projeksiyona biraz daha benzer hissettirmesini sağlamak için, gerçekleştirim uyguladığı projeksiyon türlerinin her biri için örtük dönüşümler sunar. Bu sadece bunu yapabileceğimiz anlamına gelmez.

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

Bunun yerine, dönüştürme işleçlerinin çağrının çözümlenmesinde aday olarak kullanılabilmesi için bir referans elde etmemiz gerekir.

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

Bu işe yarıyor. Örtük dönüştürme, uygulama türünden öngörülen türe (çok verimli) bir dönüştürme sağlar ve bu birçok senaryo için çok uygundur. Bu olanak olmadan, birçok gerçekleme türünü yazmak son derece zahmetli olurdu. Uygulamayı ayırmak için yalnızca winrt::make işlev şablonunu (veya winrt::make_self) kullanmanız koşuluyla, her şey yolunda demektir.

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

C++/WinRT 1.0 ile olası tuzaklar

Yine de örtük dönüşümler başınızı belaya sokabilir. Şu pek işe yaramayan yardımcı fonksiyonu düşünün.

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

Hatta sadece bu görünüşte zararsız ifade bile.

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

Ne yazık ki, bu tür kodlar örtük dönüştürme nedeniyle C++/WinRT 1.0 ile derlenmiş . (Çok ciddi) sorun, dayanak belleği geçici yığında bulunan, başvuru sayımı yapılan bir nesneye işaret eden yansıtılmış bir türü geri döndürüyor olabilmemizdir.

C++/WinRT 1.0 ile derlenen başka bir şey aşağıdadır.

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

Ham işaretçiler tehlikeli ve yoğun işçilik gerektiren hata kaynaklarıdır. İhtiyacınız yoksa kullanmayın. C++/WinRT, sizi ham işaretçiler kullanmak zorunda bırakmadan her şeyin verimli olmasını sağlamak için özellikle çaba gösterir. C++/WinRT 1.0 ile derlenen başka bir şey aşağıdadır.

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

Bu, çeşitli düzeylerde yapılan bir hatadır. Aynı nesne için iki farklı referans sayısı var. Windows Çalışma Zamanı (ve ondan önce klasik COM), std::shared_ptr ile uyumlu olmayan yerleşik bir referans sayımına dayanır. std::shared_ptr elbette birçok geçerli uygulamaya sahiptir; ancak Windows Çalışma Zamanı (ve klasik COM) nesneleri paylaşırken bu tamamen gereksizdir. Son olarak, bu C++/WinRT 1.0 ile de derlenmiş.

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

Bu yine oldukça sorgulanabilir. Benzersiz sahiplik, MyStringable'ın yerleşik referans sayacının paylaşımlı yaşam süresine zıttır.

C++/WinRT 2.0 ile çözüm

C++/WinRT 2.0 ile, uygulama türlerini doğrudan ayırmaya yönelik bu girişimlerin tümü derleyici hatasına yol açar. Bu en iyi hata türüdür ve gizemli bir çalışma zamanı hatasından sonsuz olarak daha iyidir.

Bir uygulama yapmanız gerektiğinde, yukarıda gösterildiği gibi winrt::make veya winrt::make_self kullanabilirsiniz. Şimdi, bunu yapmayı unutursanız, use_make_function_to_create_this_object adlı soyut işleve başvuruda bulunan bir derleyici hatasıyla karşılanırsınız. Tam olarak bir static_assert değil; ama çok yakın. Yine de bu, açıklanan tüm hataları algılamanın en güvenilir yoludur.

Bu, uygulamaya birkaç küçük kısıtlama uygulamamız gerektiği anlamına gelir. Doğrudan ayırmayı algılamak için geçersiz kılma olmamasını temel aldığımızdan , winrt::make işlev şablonunun bir şekilde soyut sanal işlevi bir geçersiz kılmayla karşılaması gerekir. Bunu, override sağlayan bir final sınıfını uygulamadan türeterek yapar. Bu işlemle ilgili gözlemlemem gereken birkaç şey vardır.

İlk olarak, sanal işlev yalnızca hata ayıklama derlemelerinde bulunur. Bu da algılamanın optimize edilmiş derlemelerinizde vtable boyutunu etkilemeyeceği anlamına gelir.

İkinci olarak, winrt::make tarafından kullanılan türetilmiş sınıf olduğundan final, uygulama sınıfınızı olarak finalişaretlememeyi seçmiş olsanız bile iyileştiricinin büyük olasılıkla çıkarabileceği herhangi bir devirtualizasyon gerçekleşeceği anlamına gelir. Yani bu bir gelişme. Bunun tersi, uygulamanızın olamayacağıdır final. Yine de bunun bir önemi yoktur, çünkü örneklenen tür her zaman final olacaktır.

Üçüncüsü, hiçbir şey uygulamanızdaki sanal işlevleri olarak finalişaretlemenizi engellemez. Elbette, C++/WinRT, klasik COM'dan ve uygulamanızla ilgili her şeyin sanal olma eğiliminde olduğu WRL gibi uygulamalardan çok farklıdır. C++/WinRT'de sanal dağıtım, uygulama ikili arabirimi (ABI) (her zaman final) ile sınırlıdır ve uygulama yöntemleriniz derleme süresine veya statik polimorfizme dayanır. Bu, gereksiz çalışma zamanı çok biçimliliğini önler ve ayrıca C++/WinRT uygulamanızda sanal fonksiyonlar için neredeyse hiçbir neden olmadığı anlamına gelir. Bu çok iyi bir şey ve çok daha öngörülebilir inline etmeyi sağlar.

Dördüncüsü, winrt::make türetilmiş bir sınıf eklediğinden, uygulamanızın özel bir yıkıcısı olamaz. Özel yıkıcılar, klasik COM uygulamalarında popülerdi çünkü yine her şey sanaldı ve doğrudan ham işaretçilerle çalışmak yaygındı; bu nedenle Release yerine yanlışlıkla delete çağırmak kolaydı. C++/WinRT, ham işaretçilerle doğrudan çalışmanızı zorlaştırmak için özel çaba gösterir. Ayrıca, C++/WinRT'de üzerinde potansiyel olarak delete çağırabileceğiniz ham bir işaretçi elde etmek için gerçekten ekstra çaba göstermeniz gerekir. Değer semantiği, değerler ve referanslarla; işaretçilerle ise nadiren çalıştığınız anlamına gelir.

Bu nedenle C++/WinRT, klasik COM kodu yazmanın ne anlama geldiğini önceden kavrayan varsayımlarımıza meydan okur. Ve bu son derece makul çünkü WinRT klasik COM değildir. Klasik COM, Windows Çalışma Zamanı derleme dilidir. Her gün yazdığınız kod olmamalıdır. Bunun yerine, C++/WinRT daha modern C++ gibi ve klasik COM'a çok daha az benzeyen bir kod yazmanızı sağlar.

Önemli API'ler