Diagnosi delle allocazioni diretteDiagnosing direct allocations

Come illustrato in Creare API con C++/WinRT, quando crei un oggetto di tipo implementazione, devi usare la famiglia di helper winrt::make.As explained in Author APIs with C++/WinRT, when you create an object of implementation type, you should use the winrt::make family of helpers to do so. Questo argomento illustra in dettaglio una funzionalità di C++/WinRT 2.0 che ti aiuta a diagnosticare l'errore relativo all'allocazione diretta di un oggetto di tipo implementazione nello stack.This topic goes in-depth on a C++/WinRT 2.0 feature that helps you to diagnose the mistake of directly allocating an object of implementation type on the stack.

Questi errori possono portare ad arresti anomali o danneggiamenti misteriosi, il cui debug risulta difficile e dispendioso in termini di tempo.Such mistakes can turn into mysterious crashes or corruptions that are difficult and time-consuming to debug. Si tratta quindi di una funzionalità importante, di cui è opportuno comprendere le basi.So this is an important feature, and it's worth understanding the background.

MyStringable per iniziareSetting the scene, with MyStringable

Considera innanzitutto una semplice implementazione di IStringable.First, let's consider a simple implementation of IStringable.

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

Ora immagina di dover chiamare una funzione (dall'implementazione) che prevede un elemento IStringable come argomento.Now imagine that you need to call a function (from within your implementation) that expects an IStringable as an argument.

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

Il problema è rappresentato dal fatto che il tipo MyStringable dell'esempio non è un elemento IStringable.The trouble is that our MyStringable type is not an IStringable.

  • Il tipo MyStringable dell'esempio è un'implementazione dell'interfaccia IStringable.Our MyStringable type is an implementation of the IStringable interface.
  • Il tipo IStringable è un tipo proiettato.The IStringable type is a projected type.

Importante

È importante comprendere la distinzione tra un tipo di implementazione e un tipo proiettato.It's important to understand the distinction between an implementation type and a projected type. Per i concetti e i termini fondamentali, leggi Utilizzare API con C++/WinRT e Creare API con C++/WinRT.For essential concepts and terms, be sure to read Consume APIs with C++/WinRT and Author APIs with C++/WinRT.

La differenza tra un'implementazione e la proiezione può essere difficile da cogliere.The space between an implementation and the projection can be subtle to grasp. Anzi, nel tentativo di risultare più simile alla proiezione, l'implementazione fornisce conversioni implicite a ognuno dei tipi proiettati che implementa.And in fact, to try to make the implementation feel a bit more like the projection, the implementation provides implicit conversions to each of the projected types that it implements. Questo non significa che si possa eseguire direttamente tale operazione.That doesn't mean we can simply do this.

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

È invece necessario ottenere un riferimento, in modo che gli operatori di conversione possano essere usati come candidati per risolvere la chiamata.Instead, we need to get a reference so that conversion operators may be used as candidates for resolving the call.

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

In questo modo funziona tutto.That works. Una conversione implicita fornisce una conversione (molto efficiente) dal tipo di implementazione al tipo proiettato e questo aspetto è molto utile in molti scenari.An implicit conversion provides a (very efficient) conversion from the implementation type to the projected type, and that's very convenient for many scenarios. Senza di essa, sarebbe molto difficile creare diversi tipi di implementazione.Without that facility, a lot of implementation types would prove very cumbersome to author. Se allochi l'implementazione usando solo il modello di funzione winrt::make (o winrt::make_self), non si verificano problemi.Provided that you only use the winrt::make function template (or winrt::make_self) to allocate the implementation, then all is well.

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

Potenziali rischi correlati all'uso di C++/WinRT 1.0Potential pitfalls with C++/WinRT 1.0

Le conversioni implicite possono però creare problemi.Still, implicit conversions can land you in trouble. Considera questa inutile funzione helper.Consider this unhelpful helper function.

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

Oppure questa istruzione apparentemente innocua.Or even just this apparently harmless statement.

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

Sfortunatamente questo codice è stato compilato con C++/WinRT 1.0 a causa di tale conversione implicita.Unfortunately, code like that did compile with C++/WinRT 1.0, because of that implicit conversion. Il problema (molto serio) è dato dal fatto che potenzialmente viene restituito un tipo proiettato che punta a un oggetto con conteggio dei riferimenti la cui memoria di supporto si trova nello stack temporaneo.The (very serious) problem is that we're potentially returning a projected type that points to a reference-counted object whose backing memory is on the ephemeral stack.

Di seguito è riportato altro codice che è stato compilato con C++/WinRT 1.0.Here's something else that compiled with C++/WinRT 1.0.

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

I puntatori non elaborati sono una fonte di bug pericolosa e impegnativa.Raw pointers are dangerous and labor-intensive source of bugs. Non usarli se non è necessario.Don't use them if you don't need to. C++/WinRT tenta di rendere tutto efficiente senza obbligarti mai a usare puntatori non elaborati.C++/WinRT goes out of its way to make everything efficient without ever forcing you into using raw pointers. Di seguito è riportato altro codice che è stato compilato con C++/WinRT 1.0.Here's something else that compiled with C++/WinRT 1.0.

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

Questo è un errore a diversi livelli.This is a mistake on several levels. Per lo stesso oggetto sono presenti due diversi conteggi dei riferimenti.We have two different reference counts for the same object. Windows Runtime (e prima di esso COM classico) è basato su un conteggio dei riferimenti intrinseco che non è compatibile con std::shared_ptr.The Windows Runtime (and classic COM before it) is based on an intrinsic reference count that's not compatible with std::shared_ptr. std::shared_ptr ha ovviamente molte applicazioni valide, ma è assolutamente inutile quando condividi oggetti di Windows Runtime (e di COM classico).std::shared_ptr has, of course, many valid applications; but it's entirely unnecessary when you're sharing Windows Runtime (and classic COM) objects. Con C++/WinRT 1.0 è stato compilato anche il codice seguente.Finally, this also compiled with C++/WinRT 1.0.

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

Questa è un'altra situazione piuttosto dubbia.This is again rather questionable. La proprietà univoca è in contrapposizione con la durata condivisa del conteggio intrinseco dei riferimenti di MyStringable.The unique ownership is in opposition to the shared lifetime of the MyStringable's intrinsic reference count.

Soluzione con C++/WinRT 2.0The solution with C++/WinRT 2.0

Con C++/WinRT 2.0 tutti questi tentativi di allocare direttamente i tipi di implementazione portano a un errore del compilatore.With C++/WinRT 2.0, all of these attempts to directly allocate implementation types leads to a compiler error. Si tratta del miglior tipo di errore che possa essere generato, decisamente più gestibile rispetto a un misterioso bug di runtime.That's the best kind of error, and infinitely better than a mysterious runtime bug.

Ogni volta che devi effettuare un'implementazione, puoi usare semplicemente winrt::make o winrt::make_self, come illustrato in precedenza.Whenever you need to make an implementation, you can simply use winrt::make or winrt::make_self, as shown above. E ora, se ti dimentichi di procedere in questo modo, riceverai un errore del compilatore che allude a tale condizione con un riferimento a una funzione astratta denominata use_make_function_to_create_this_object.And now, if you forget to do so, then you'll be greeted with a compiler error alluding to this with a reference to an abstract function named use_make_function_to_create_this_object. Anche se non corrisponde esattamente a un elemento static_assert, è molto simile.It's not exactly a static_assert; but it's close. Questo è in ogni caso il modo più affidabile per rilevare tutti gli errori descritti.Still, this is the most reliable way of detecting all of the mistakes described.

È quindi necessario porre alcune piccole limitazioni relative all'implementazione.It does mean that we need to place a few minor constraints on the implementation. Poiché ci si basa sull'assenza di un override per rilevare l'allocazione diretta, il modello di funzione winrt::make deve soddisfare in qualche modo la funzione virtuale astratta con un override.Given that we're relying on the absence of an override to detect direct allocation, the winrt::make function template must somehow satisfy the abstract virtual function with an override. A tale scopo, deriva dall'implementazione con una classe final che fornisce l'override.It does so by deriving from the implementation with a final class that provides the override. Relativamente a questo processo, è necessario osservare alcuni aspetti.There are a few things to observe about this process.

In primo luogo, la funzione virtuale è presente solo nelle build di debug.First, the virtual function is only present in debug builds. Il rilevamento pertanto non influirà sulle dimensioni dell'elemento vtable nelle build ottimizzate.Which means that detection isn't going to affect the size of the vtable in your optimized builds.

In secondo luogo, poiché la classe derivata usata da winrt::make è final, qualsiasi devirtualizzazione che l'ottimizzatore può eventualmente dedurre verrà eseguita anche se in precedenza hai scelto di non contrassegnare la classe di implementazione come final.Second, since the derived class that winrt::make uses is final, it means that any devirtualization that the optimizer can possibly deduce will happen even if you previously chose not to mark your implementation class as final. Si tratta quindi di un miglioramento.So that's an improvement. Il contrario è che l'implementazione non può essere final.The converse is that your implementation can't be final. Anche in questo caso non ci saranno conseguenze perché il tipo di cui è stata creata un'istanza sarà sempre final.Again, that's of no consequence because the instantiated type will always be final.

In terzo luogo, niente può impedirti di contrassegnare eventuali funzioni virtuali nell'implementazione come final.Third, nothing prevents you from marking any virtual functions in your implementation as final. C++/WinRT ovviamente è molto diverso da COM classico e dalle implementazioni come WRL, in cui ogni elemento relativo all'implementazione tende a essere virtuale.Of course, C++/WinRT is very different from classic COM and implementations such as WRL, where everything about your implementation tends to be virtual. In C++/WinRT l'invio virtuale è limitato all'interfaccia ABI (Application Binary Interface), che è sempre final, e i metodi di implementazione si basano sul polimorfismo statico o della fase di compilazione.In C++/WinRT, the virtual dispatch is limited to the application binary interface (ABI) (which is always final), and your implementation methods rely on compile-time or static polymorphism. In questo modo si evita il polimorfismo di runtime non necessario ed è raro che si debbano inserire funzioni virtuali nell'implementazione C++/WinRT.That avoids unnecessary runtime polymorphism, and also means that there's precious little reason for virtual functions in your C++/WinRT implementation. Si tratta di un aspetto molto positivo, che rende decisamente più prevedibile l'inlining.Which is a very good thing, and leads to far more predictable inlining.

In quarto luogo, poiché winrt::make inserisce una classe derivata, l'implementazione non può avere un distruttore privato.Fourth, since winrt::make injects a derived class, your implementation can't have a private destructor. I distruttori privati erano molto diffusi nelle implementazioni di COM classico perché, anche in questo caso, tutti gli elementi erano virtuali ed era pratica comune gestire direttamente i puntatori non elaborati. Era quindi facile chiamare accidentalmente delete anziché Release.Private destructors were popular with classic COM implementations because, again, everything was virtual, and it was common to deal directly with raw pointers and thus was easy to accidentally call delete instead of Release. C++/WinRT cerca di metterti in condizione di non gestire direttamente i puntatori non elaborati.C++/WinRT goes out of its way to make it hard for you to deal directly with raw pointers. Dovresti quindi cercare in tutti i modi di ottenere in C++/WinRT un puntatore non elaborato su cui chiamare potenzialmente delete.And you'd have to really go out of your way to get a raw pointer in C++/WinRT that you could potentially call delete on. La semantica dei valori significa che stai gestendo valori e riferimenti e raramente i puntatori.Value semantics means that you're dealing with values and references; and rarely with pointers.

C++/WinRT pertanto va contro le nozioni preconcette relative a cosa significhi scrivere codice di COM classico.So, C++/WinRT challenges our preconceived notions of what it means to write classic COM code. Questo è assolutamente ragionevole perché WinRT non è COM classico.And that's perfectly reasonable because WinRT is not classic COM. COM classico è il linguaggio assembly di Windows Runtime.Classic COM is the assembly language of the Windows Runtime. Non dovrebbe perciò costituire il codice che scrivi ogni giorno.It shouldn't be the code you write every day. C++/WinRT ti porta invece a scrivere codice più simile a C++ moderno e molto più distante da COM classico.Instead, C++/WinRT gets you to write code that's more like modern C++, and far less like classic COM.

API importantiImportant APIs