Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
A C++/WinRT-lel rendelkező Author API-kban leírtak szerint a megvalósítási típusú objektumok létrehozásakor a winrt::make family of helpers (Segítők családja) parancsot kell használnia. Ez a témakör részletesen foglalkozik a C++/WinRT 2.0 funkcióval, amely segít diagnosztizálni azt a hibát, hogy közvetlenül kiosztott egy implementációs típusú objektumot a veremen.
Az ilyen hibák rejtélyes összeomlásokká vagy sérüléssé alakulhatnak, amelyek hibakeresése nehéz és időigényes. Tehát ez egy fontos funkció, és érdemes megérteni a hátteret.
A jelenet beállítása a MyStringable használatával
Először is vegyük fontolóra az IStringable egyszerű implementálását.
struct MyStringable : implements<MyStringable, IStringable>
{
winrt::hstring ToString() const { return L"MyStringable"; }
};
Most képzelje el, hogy egy függvényt kell meghívnia (a megvalósításon belülről), amely egy IStringable-t vár argumentumként.
void Print(IStringable const& stringable)
{
printf("%ls\n", stringable.ToString().c_str());
}
A baj az, hogy a MyStringabletípusunk nemIStringable.
- A MyStringable típus az IStringable interfész implementációja.
- Az IStringable típus egy előrejelzett típus.
Fontos
Fontos megérteni a megvalósítási típus és a tervezett típus közötti különbséget. Az alapvető fogalmak és kifejezések megismeréséhez feltétlenül olvassa el a API-k használata C++/WinRT-vel és a API-k létrehozása C++/WinRT-vel című témaköröket.
A megvalósítás és a kivetítés közötti tér finoman megragadható. Sőt, hogy az implementáció egy kicsit jobban hasonlítson a leképezésre, implicit konverziókat biztosít mindegyik általa implementált leképezett típusra. Ez nem jelenti azt, hogy egyszerűen meg tudjuk csinálni.
struct MyStringable : implements<MyStringable, IStringable>
{
winrt::hstring ToString() const;
void Call()
{
Print(this);
}
};
Ehelyett egy referenciát kell beszereznünk, hogy a konverziós operátorok a hívás megoldására jelöltként használhatók legyenek.
void Call()
{
Print(*this);
}
Ez működik. Az implicit átalakítás (nagyon hatékony) átalakítást biztosít a megvalósítási típusról a tervezett típusra, és ez számos forgatókönyv esetében nagyon kényelmes. E nélkül a létesítmény nélkül sok megvalósítási típus nagyon nehézkesnek bizonyulna a szerző számára. Feltéve, hogy csak a winrt::make függvénysablont (vagy winrt::make_self) használja a megvalósítás lefoglalásához, akkor minden rendben van.
IStringable stringable{ winrt::make<MyStringable>() };
Lehetséges buktatók a C++/WinRT 1.0-val
Mégis, az implicit konverziók bajba sodorhatnak. Fontolja meg ezt a nem túl hasznos segédfüggvényt.
IStringable MakeStringable()
{
return MyStringable(); // Incorrect.
}
Vagy akár csak ez a látszólag ártalmatlan állítás.
IStringable stringable{ MyStringable() }; // Also incorrect.
Sajnos az ehhez hasonló kód a C++/WinRT 1.0 verzióval készült , az implicit átalakítás miatt. A (nagyon súlyos) probléma az, hogy potenciálisan egy olyan kivetített típust adunk vissza, amely egy referenciaszámlált objektumra mutat, és ennek a mögöttes memóriaterülete a tranziens veremen található.
Íme valami más, amely a C++/WinRT 1.0-val készült.
MyStringable* stringable{ new MyStringable() }; // Very inadvisable.
A nyers pointerek veszélyesek, és sok hibaforrást jelentenek. Ne használja őket, ha nem kell. A C++/WinRT mindent megtesz azért, hogy minden hatékony legyen, anélkül hogy valaha is nyers mutatók használatára kényszerítene. Íme valami más, amely a C++/WinRT 1.0-val készült.
auto stringable{ std::make_shared<MyStringable>(); } // Also very inadvisable.
Ez hiba több szinten. Ugyanahhoz az objektumhoz két különböző referenciaszámmal rendelkezünk. A Windows-futtatókörnyezet (és előtte a klasszikus COM) egy beépített referenciaszámlálón alapul, amely nem kompatibilis a std::shared_ptr típussal. std::shared_ptr természetesen számos érvényes alkalmazás van; de teljesen felesleges, ha Windows-futtatókörnyezet (és klasszikus COM) objektumokat oszt meg. Végül ezt a C++/WinRT 1.0-val is lefordítottuk.
auto stringable{ std::make_unique<MyStringable>() }; // Highly dubious.
Ez ismét meglehetősen megkérdőjelezhető. A kizárólagos tulajdonlás ellentétben áll a MyStringable belső referencia-számlálójának megosztott élettartamával.
A megoldás a C++/WinRT 2.0-val
A C++/WinRT 2.0-val a megvalósítási típusok közvetlen lefoglalására tett összes kísérlet fordítóhibahoz vezet. Ez a legjobb hiba, és végtelenül jobb, mint egy titokzatos futtatókörnyezeti hiba.
Amikor implementálásra van szüksége, egyszerűen használhatja a winrt::make vagy winrt::make_self parancsot a fent látható módon. És most, ha elfelejti ezt megtenni, akkor egy fordítóhiba jelenik meg, amely erre utal egy use_make_function_to_create_this_object nevű absztrakt függvényre való hivatkozással. Ez nem pontosan egy static_assert; de közel van. Mégis ez a legmegbízhatóbb módszer az összes leírt hiba észlelésére.
Ez azt jelenti, hogy néhány kisebb korlátozást kell helyeznünk a megvalósításra. Mivel a közvetlen foglalás észleléséhez a felülbírálás hiányára támaszkodunk, a winrt::make függvénysablonnak valahogyan felülbírálást kell biztosítania az absztrakt virtuális függvényhez. Ezt úgy teszi, hogy az implementációból származtat egy final osztályt, amely biztosítja a felülírást. Van néhány dolog, amit meg kell figyelni erről a folyamatról.
Először is a virtuális függvény csak hibakeresési buildekben van jelen. Ez azt jelenti, hogy az észlelés nem befolyásolja a virtuális tábla méretét az optimalizált buildekben.
Másodszor, mivel a winrt::make által használt finalszármaztatott osztály azt jelenti, hogy minden olyan devirtualizáció, amelyet az optimalizáló esetleg le tud következtetni, akkor is megtörténik, ha korábban úgy döntött, hogy nem jelöli meg a megvalósítási osztályt final. Szóval ez javulás. Ennek az ellenkezője az, hogy a megvalósítás nem lehetfinal. Ennek szintén nincs jelentősége, mert a példányosított típus mindig final lesz.
Harmadszor: semmi sem akadályozza meg abban, hogy a megvalósításában tetszőleges virtuális függvényt final-ként jelöljön meg. A C++/WinRT természetesen nagyon különbözik a klasszikus COM-tól és az olyan implementációktól, mint a WRL, ahol az implementáció minden része virtuális. A C++/WinRT-ben a virtuális küldés az alkalmazás bináris felületére (ABI) korlátozódik (ami mindig final), és a megvalósítási módszerek fordítási időre vagy statikus polimorfizmusra támaszkodnak. Ez elkerüli a felesleges futásidejű polimorfizmust, és azt is jelenti, hogy a C++/WinRT implementációban értékes kevés oka van a virtuális függvényeknek. Ami nagyon jó dolog, és sokkal kiszámíthatóbb inlininghez vezet.
Negyedszer, mivel a winrt::make injektál egy származtatott osztályt, az implementáció nem rendelkezhet privát destruktorsal. A private destruktorok népszerűek voltak a klasszikus COM-implementációkban, mert itt is minden virtuális volt, és gyakran közvetlenül nyers mutatókkal dolgoztak, így könnyű volt véletlenül a delete elemet meghívni a Release helyett. A C++/WinRT mindent megtesz annak érdekében, hogy megnehezítse a nyers mutatók közvetlen kezelését. És tényleg külön erőfeszítést kellene tennie ahhoz, hogy a C++/WinRT-ben olyan nyers mutatóhoz jusson, amelyen esetleg meg lehet hívni a delete elemet. Az értékszemantika azt jelenti, hogy értékekkel és hivatkozásokkal foglalkozik; és ritkán mutatókkal.
A C++/WinRT tehát megkérdőjelezi a klasszikus COM-kód írásának lényegét. És ez teljesen ésszerű, mert a WinRT nem klasszikus COM. A klasszikus COM a Windows-futtatókörnyezet szerelvénynyelve. Nem annak a kódnak kellene lennie, amelyet nap mint nap írsz. Ehelyett a C++/WinRT segítségével olyan kódot írhat, amely jobban hasonlít a modern C++-ra, és sokkal kevésbé hasonlít a klasszikus COM-hoz.
Fontos API-k
Kapcsolódó témakörök
Windows developer