Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Som beskrivs i Api:er för författare med C++/WinRTbör du använda winrt::make familj med hjälphjälpare för att göra det när du skapar ett objekt av implementeringstyp. Det här avsnittet går djupare på en C++/WinRT 2.0-funktion som hjälper dig att diagnostisera misstaget att direkt allokera ett objekt av implementeringstyp på stacken.
Sådana misstag kan förvandlas till mystiska krascher eller skador som är svåra och tidskrävande att felsöka. Det här är därför en viktig funktion och det är värt att förstå bakgrunden.
Att skapa en scen med MyStringable
Först ska vi överväga en enkel implementering av IStringable.
struct MyStringable : implements<MyStringable, IStringable>
{
winrt::hstring ToString() const { return L"MyStringable"; }
};
Anta nu att du måste anropa en funktion som, inifrån din implementering, förväntar sig en IStringable- som ett argument.
void Print(IStringable const& stringable)
{
printf("%ls\n", stringable.ToString().c_str());
}
Problemet är att vår MyStringable typ är inte en IStringable.
- Vår MyStringable--typ är en implementering av IStringable--gränssnittet.
- Typen IStringable är en projicerad typ.
Viktigt!
Det är viktigt att förstå skillnaden mellan en -implementeringstyp och en projekterad typ. För viktiga begrepp och termer med API:er, läs Använda API:er med C++/WinRT och Skriv API:er med C++/WinRT.
Utrymmet mellan en implementering och projektionen kan vara subtilt att förstå. Och för att försöka få implementeringen att kännas lite mer som projektionen ger implementeringen implicita konverteringar till var och en av de planerade typerna som implementeras. Det betyder inte att vi bara kan göra det här.
struct MyStringable : implements<MyStringable, IStringable>
{
winrt::hstring ToString() const;
void Call()
{
Print(this);
}
};
I stället måste vi hämta en referens så att konverteringsoperatorer kan användas som kandidater för att lösa anropet.
void Call()
{
Print(*this);
}
Det fungerar. En implicit konvertering ger en (mycket effektiv) konvertering från implementeringstypen till den planerade typen, och det är mycket praktiskt för många scenarier. Utan den möjligheten skulle många implementeringstyper visa sig vara mycket besvärliga att skapa. Förutsatt att du bara använder winrt::make funktionsmall (eller winrt::make_self) för att allokera implementeringen är allt bra.
IStringable stringable{ winrt::make<MyStringable>() };
Potentiella fallgropar med C++/WinRT 1.0
Men implicita konverteringar kan få dig att hamna i trubbel. Tänk på den här hjälpfunktionen som inte hjälper dig.
IStringable MakeStringable()
{
return MyStringable(); // Incorrect.
}
Eller till och med bara detta till synes ofarliga uttalande.
IStringable stringable{ MyStringable() }; // Also incorrect.
Tyvärr kompilerar kod som den med C++/WinRT 1.0 på grund av den implicita konverteringen. Det (mycket allvarliga) problemet är att vi potentiellt returnerar en projicerad typ som pekar på ett referensräknat objekt vars bakomliggande minne finns på den flyktiga stacken.
Här är något annat som kompilerats med C++/WinRT 1.0.
MyStringable* stringable{ new MyStringable() }; // Very inadvisable.
Råpekare är farliga och en arbetsintensiv källa till buggar. Använd dem inte om du inte behöver det. C++/WinRT gör allt effektivt utan att någonsin tvinga dig att använda råpekare. Här är något annat som kompilerats med C++/WinRT 1.0.
auto stringable{ std::make_shared<MyStringable>(); } // Also very inadvisable.
Detta är ett misstag på flera nivåer. Vi har två olika referensantal för samma objekt. Windows Runtime (och klassisk COM före det) baseras på ett inbyggt referensantal som inte är kompatibelt med std::shared_ptr. std::shared_ptr har naturligtvis många giltiga program; men det är helt onödigt när du delar Windows Runtime-objekt (och klassiska COM). Slutligen kompilerades detta också med C++/WinRT 1.0.
auto stringable{ std::make_unique<MyStringable>() }; // Highly dubious.
Detta är återigen ganska tvivelaktigt. Det unika ägandet står i motsats till den gemensamma livslängden för MyStringable's inbyggda referensantal.
Lösningen med C++/WinRT 2.0
Med C++/WinRT 2.0 leder alla dessa försök att direkt allokera implementeringstyper till ett kompilatorfel. Det är den bästa typen av fel, och oändligt mycket bättre än en mystisk körningsbugg.
När du behöver göra en implementering kan du helt enkelt använda winrt::make eller winrt::make_self, enligt ovan. Och nu, om du glömmer att göra det, kommer du att hälsas med ett kompilatorfel som anspelar på detta med en referens till en abstrakt funktion med namnet use_make_function_to_create_this_object. Det är inte precis en static_assert; men ändå nära. Ändå är detta det mest tillförlitliga sättet att upptäcka alla misstag som beskrivs.
Det innebär att vi måste införa några mindre begränsningar för genomförandet. Med tanke på att vi förlitar oss på frånvaron av en överskrivning för att identifiera direkt allokering måste winrt::make funktionsmall på något sätt uppfylla den abstrakta virtuella metoden med en överskrivning. Det gör det genom att ärva från implementeringen med en final-klass som ger en åsidosättning. Det finns några saker att observera om den här processen.
För det första finns den virtuella funktionen bara i felsökningsversioner. Vilket innebär att detekteringen inte kommer att påverka storleken på v-tabellen i dina optimerade versioner.
För det andra, eftersom den härledda klassen som winrt::make använder är finalinnebär det att alla devirtualiseringar som optimeraren eventuellt kan härleda sker även om du tidigare valde att inte markera implementeringsklassen som final. Så det är en förbättring. Det omvända är att implementeringen inte kan vara final. Återigen har det ingen betydelse eftersom den instansierade typen alltid kommer att vara final.
För det tredje hindrar ingenting dig från att markera några virtuella funktioner i implementeringen som final. Naturligtvis skiljer sig C++/WinRT mycket från klassisk COM och implementeringar som WRL, där allt om din implementering tenderar att vara virtuellt. I C++/WinRT är den virtuella sändningen begränsad till det binära programgränssnittet (ABI) (som alltid är final), och implementeringsmetoderna förlitar sig på kompileringstid eller statisk polymorfism. Det undviker onödig körningspolymorfism och innebär också att det finns mycket liten anledning till virtuella funktioner i C++/WinRT-implementeringen. Vilket är mycket bra, och leder till mycket mer förutsägbar inlining.
För det fjärde, eftersom winrt::make matar in en härledd klass, kan implementeringen inte ha en privat destruktor. Privata destruktorer var populära med klassiska COM-implementeringar eftersom allt var virtuellt igen, och det var vanligt att hantera råpekare direkt och därför var det lätt att av misstag anropa delete istället för Release. C++/WinRT går utöver det vanliga för att göra det svårt för dig att hantera råpekare direkt. Och du måste verkligen gå ur vägen för att få en rå pekare i C ++/WinRT som du potentiellt kan anropa delete på. Värdesemantik innebär att du hanterar värden och referenser. och sällan med pekare.
Därför utmanar C++/WinRT våra förutfattade meningar om vad det innebär att skriva klassisk COM-kod. Och det är helt rimligt eftersom WinRT inte är klassisk COM. Klassisk COM är sammansättningsspråket för Windows Runtime. Det borde inte vara koden du skriver varje dag. I stället får C++/WinRT dig att skriva kod som mer liknar modern C++, och mycket mindre som klassisk COM.