Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Jak je vysvětleno v API Autorů sC++/WinRT , při vytváření objektu implementačního typu byste měli použít winrt::make sadu pomocníků. Toto téma podrobně popisuje funkci C++/WinRT 2.0, která vám pomůže diagnostikovat chybu přímého přidělování objektu typu implementace v zásobníku.
Takové chyby se můžou změnit na záhadné havárie nebo poškození, které jsou obtížné a časově náročné na ladění. Proto je to důležitá funkce, která stojí za pochopení pozadí.
Nastavení scény s MyStringable
Nejprve uvažujeme o jednoduché implementaci IStringable.
struct MyStringable : implements<MyStringable, IStringable>
{
winrt::hstring ToString() const { return L"MyStringable"; }
};
Nyní si představte, že potřebujete zavolat funkci (z vnitřku vaší implementace), která očekává IStringable jako argument.
void Print(IStringable const& stringable)
{
printf("%ls\n", stringable.ToString().c_str());
}
Problém je, že náš typ
- Náš typ MyStringable je implementace rozhraní IStringable.
- Typ IStringable je projektovaný typ.
Důležité
Je důležité pochopit rozdíl mezi typem implementace a projektovaným typem. Pro základní koncepty a termíny si nezapomeňte přečíst článek "Využívání rozhraní API pomocí C++/WinRT" a článek "Vytváření rozhraní API pomocí C++/WinRT".
Prostor mezi implementací a projekcí může být jemný pro pochopení. Aby implementace více připomínala projekci, ve skutečnosti poskytuje implicitní převody na každý z implementovaných projektovaných typů. To neznamená, že to prostě můžeme udělat.
struct MyStringable : implements<MyStringable, IStringable>
{
winrt::hstring ToString() const;
void Call()
{
Print(this);
}
};
Místo toho potřebujeme získat referenci, aby operátory převodu mohly být použity jako kandidáti při řešení volání.
void Call()
{
Print(*this);
}
To funguje. Implicitní převod poskytuje (velmi efektivní) převod z typu implementace na projektovaný typ a je velmi pohodlný pro mnoho scénářů. Bez tohoto zařízení by se hodně typů implementace ukázalo jako velmi těžkopádné pro autora. Za předpokladu, že k přidělení implementace použijete pouze winrt::make šablonu funkce (nebo winrt::make_self), pak je vše v pořádku.
IStringable stringable{ winrt::make<MyStringable>() };
Potenciální nástrahy C++/WinRT 1.0
Implicitní převody vás ale můžou dostat do problémů. Zvažte tuto nepoužitou pomocnou funkci.
IStringable MakeStringable()
{
return MyStringable(); // Incorrect.
}
Nebo dokonce jen tento zdánlivě neškodný výrok.
IStringable stringable{ MyStringable() }; // Also incorrect.
Kód podobný této bohužel zkompiloval pomocí C++/WinRT 1.0, protože tento implicitní převod. Problém (velmi závažný) je, že potenciálně vracíme projektovaný typ, který odkazuje na objekt počítaný odkazem, jehož záložní paměť je v dočasném zásobníku.
Tady je ještě něco, co bylo zkompilováno s C++/WinRT 1.0.
MyStringable* stringable{ new MyStringable() }; // Very inadvisable.
Nezpracované ukazatele jsou nebezpečným a pracně náročným zdrojem chyb. Nepoužívejte je, pokud je nepotřebujete. C++/WinRT vynakládá veškeré úsilí, aby vše bylo efektivní, aniž byste museli používat surové ukazatele. Tady je ještě něco, co bylo zkompilováno s C++/WinRT 1.0.
auto stringable{ std::make_shared<MyStringable>(); } // Also very inadvisable.
To je chyba na několika úrovních. Pro stejný objekt máme dva různé počty odkazů. Modul Windows Runtime (a klasický model COM před ním) je založen na vnitřním počtu odkazů, které nejsou kompatibilní s std::shared_ptr. std::shared_ptr má samozřejmě mnoho platných aplikací; ale při sdílení objektů prostředí Windows Runtime (a klasických objektů COM) je zcela zbytečné. Nakonec se tento příkaz zkompiloval také pomocí C++/WinRT 1.0.
auto stringable{ std::make_unique<MyStringable>() }; // Highly dubious.
To je opět poměrně pochybné. Jedinečné vlastnictví je v rozporu se sdílenou životností a intrinsickým počítáním referencí MyStringable.
Řešení s C++/WinRT 2.0
U C++/WinRT 2.0 všechny tyto pokusy o přímé přidělení typů implementace vedou k chybě kompilátoru. To je nejlepší druh chyby a nekonečně lepší než záhadná chyba modulu runtime.
Kdykoli potřebujete provést implementaci, můžete jednoduše použít winrt::make nebo winrt::make_self, jak je znázorněno výše. A teď, pokud to zapomenete udělat, pak se vám zobrazí chyba kompilátoru s odkazem na abstraktní funkci s názvem use_make_function_to_create_this_object. Není to přesně static_assert; ale je blízko. Přesto je to nejspolehlivější způsob zjištění všech popsaných chyb.
Znamená to, že musíme na implementaci umístit několik menších omezení. Vzhledem k tomu, že jsme závislí na neexistenci přepsání k detekci přímého přidělení, musí šablona funkce winrt::make nějak splnit abstraktní virtuální funkci přepsáním. Provádí to odvozením z implementace třídy final, která umožňuje přepsání. Existuje několik věcí, které je třeba o tomto procesu pozorovat.
Nejprve je virtuální funkce přítomna pouze ve verzích pro ladění. To znamená, že detekce nebude mít vliv na velikost virtuální tabulky v optimalizovaných buildech.
Za druhé, protože odvozená třída, kterou používá winrt::make, je final, znamená to, že jakákoliv devirtualizace, kterou optimalizátor dokáže odvodit, se uskuteční, i když jste se dříve rozhodli neoznačit vaši implementační třídu jako final. Takže to je zlepšení. Naopak je, že vaše implementace nemůže být final. To není podstatné, protože instance typu bude vždy final.
Za třetí, nic vám nebrání v označení všech virtuálních funkcí v implementaci jako final. Samozřejmě, C++/WinRT je velmi odlišný od klasického COM a implementací, jako je WRL, kde má vaše implementace tendenci být virtuální. V jazyce C++/WinRT je virtuální odesílání omezené na binární rozhraní aplikace (ABI), což je vždy final) a vaše metody implementace spoléhají na kompilaci a statický polymorfismus. Tím se vyhnete zbytečnému polymorfismu za běhu a také to znamená, že ve vaší implementaci C++/WinRT není téměř žádný důvod pro použití virtuálních funkcí. Což je velmi dobrá věc a vede k mnohem předvídatelnějšímu vkládání.
Začtvrté, protože winrt::make vloží odvozenou třídu, vaše implementace nemůže mít privátní destruktor. Soukromé destruktory byly oblíbené u klasických implementací COM, protože všechno bylo virtuální a bylo běžné pracovat přímo s hrubými ukazateli, což zvyšovalo riziko náhodného zavolání delete místo Release. C++/WinRT vynakládá velké úsilí, aby vám ztížil práci přímo se surovými ukazateli. A museli byste opravdu jít ven, abyste získali nezpracovaný ukazatel v C++/WinRT, který byste mohli potenciálně zavolat delete. Sémantika hodnot znamená, že pracujete s hodnotami a odkazy; zřídka s ukazateli.
C++/WinRT tedy zpochybňuje naše předem vytvořené představy o tom, co znamená psát klasický kód COM. A to je naprosto logické, protože WinRT není klasický COM. Klasický COM je assembler prostředí Windows Runtime. Neměl by to být kód, který píšete každý den. Místo toho vám C++/WinRT umožní psát kód, který je spíš moderní jazyk C++ a mnohem méně podobný klasickému modelu COM.
Důležitá rozhraní API
Související témata
- Práce s rozhraními API v C++/WinRT
- Tvorba rozhraní API pomocí C++/WinRT