Riferimenti sicuri e deboli in C++/WinRTStrong and weak references in C++/WinRT

Windows Runtime è un sistema con conteggio dei riferimenti e in un sistema di questo tipo è importante conoscere il significato e la differenza tra riferimenti sicuri e riferimenti deboli, così come i riferimenti di nessuno di questi tipi, come il puntatore this implicito.The Windows Runtime is a reference-counted system; and in such a system it's important for you to know about the significance of, and distinction between, strong and weak references (and references that are neither, such as the implicit this pointer). Come si vedrà in questo argomento, sapere come gestire correttamente questi riferimenti può fare la differenza tra un sistema affidabile che viene eseguito senza problemi e uno con arresti anomali imprevedibili.As you'll see in this topic, knowing how to manage these references correctly can mean the difference between a reliable system that runs smoothly, and one that crashes unpredictably. Offrendo funzioni helper con supporto avanzato nella proiezione del linguaggio, C++/WinRT ti viene incontro per il lavoro di creazione di sistemi più complessi in modo semplice e corretto.By providing helper functions that have deep support in the language projection, C++/WinRT meets you halfway in your work of building more complex systems simply and correctly.

Accesso sicuro al puntatore this in una coroutine membro di classeSafely accessing the this pointer in a class-member coroutine

Per altre informazioni sulle coroutine ed esempi di codice, vedi Concorrenza e operazioni asincrone con C++/WinRT.For more info about coroutines, and code examples, see Concurrency and asynchronous operations with C++/WinRT.

Il listato di codice seguente mostra un esempio tipico di una coroutine che è una funzione membro di una classe.The code listing below shows a typical example of a coroutine that's a member function of a class. Puoi copiare e incollare questo esempio nei file specificati in un nuovo progetto Applicazione console di Windows (C++/WinRT) .You can copy-paste this example into the specified files in a new Windows Console Application (C++/WinRT) project.

// pch.h
#pragma once
#include <iostream>
#include <winrt/Windows.Foundation.h>

// main.cpp : Defines the entry point for the console application.
#include "pch.h"

using namespace winrt;
using namespace Windows::Foundation;
using namespace std::chrono_literals;

struct MyClass : winrt::implements<MyClass, IInspectable>
{
    winrt::hstring m_value{ L"Hello, World!" };

    IAsyncOperation<winrt::hstring> RetrieveValueAsync()
    {
        co_await 5s;
        co_return m_value;
    }
};

int main()
{
    winrt::init_apartment();

    auto myclass_instance{ winrt::make_self<MyClass>() };
    auto async{ myclass_instance->RetrieveValueAsync() };

    winrt::hstring result{ async.get() };
    std::wcout << result.c_str() << std::endl;
}

MyClass::RetrieveValueAsync esegue alcune elaborazioni e alla fine restituisce una copia del membro dati MyClass::m_value.MyClass::RetrieveValueAsync spends some time working, and eventually it returns a copy of the MyClass::m_value data member. La chiamata di RetrieveValueAsync causa la creazione di un oggetto asincrono e tale oggetto ha un puntatore this implicito, tramite il quale avviene alla fine l'accesso a m_value.Calling RetrieveValueAsync causes an asynchronous object to be created, and that object has an implicit this pointer (through which, eventually, m_value is accessed).

Tieni presente che in una coroutine l'esecuzione è sincrona fino al primo punto di sospensione, dove il controllo viene restituito al chiamante.Remember that, in a coroutine, execution is synchronous up until the first suspension point, where control is returned to the caller. In RetrieveValueAsync, la prima occorrenza di co_await è il primo punto di sospensione.In RetrieveValueAsync, the first co_await is the first suspension point. Entro il tempo in cui la coroutine riprende l'esecuzione (in questo caso cinque minuti dopo), può essere accaduta qualsiasi cosa al puntatore this implicito tramite il quale si accede a m_value.By the time the coroutine resumes (around five seconds later, in this case), anything might have happened to the implicit this pointer through which we access m_value.

Ecco la sequenza di eventi completa.Here's the full sequence of events.

  1. In main viene creata un'istanza di MyClass (myclass_instance).In main, an instance of MyClass is created (myclass_instance).
  2. Viene creato l'oggetto async che punta a myclass_instance tramite il relativo puntatore this.The async object is created, pointing (via its this) to myclass_instance.
  3. La funzione winrt::Windows::Foundation::IAsyncAction::get raggiunge il primo punto di sospensione, si blocca per pochi secondi e quindi restituisce il risultato di RetrieveValueAsync.The winrt::Windows::Foundation::IAsyncAction::get function hits its first suspension point, blocks for a few seconds, and then returns the result of RetrieveValueAsync.
  4. RetrieveValueAsync restituisce il valore di this->m_value.RetrieveValueAsync returns the value of this->m_value.

Il passaggio 4 è sicuro purché this rimanga valido.Step 4 is safe only as long as this remains valid.

Ma cosa accade se l'istanza della classe viene eliminata definitivamente prima del completamento dell'operazione asincrona?But what if the class instance is destroyed before the async operation completes? Sono tantissimi i modi in cui l'istanza della classe può uscire dall'ambito prima del completamento del metodo asincrono,There are all kinds of ways the class instance could go out of scope before the asynchronous method has completed. ma possiamo fare una simulazione impostando l'istanza della classe su nullptr.But we can simulate it by setting the class instance to nullptr.

int main()
{
    winrt::init_apartment();

    auto myclass_instance{ winrt::make_self<MyClass>() };
    auto async{ myclass_instance->RetrieveValueAsync() };
    myclass_instance = nullptr; // Simulate the class instance going out of scope.

    winrt::hstring result{ async.get() }; // Behavior is now undefined; crashing is likely.
    std::wcout << result.c_str() << std::endl;
}

Dopo il punto in cui viene eliminata l'istanza della classe, sembra che non ci siano altri riferimenti diretti.After the point where we destroy the class instance, it looks like we don't directly refer to it again. Ma l'oggetto asincrono ha ovviamente un puntatore this a tale classe e tenta di usarlo per copiare il valore archiviato all'interno dell'istanza della classe.But of course the asynchronous object has a this pointer to it, and tries to use that to copy the value stored inside the class instance. Le coroutine è una funzione membro e si aspetta di poter usare il relativo puntatore this impunemente.The coroutine is a member function, and it expects to be able to use its this pointer with impunity.

Con questa modifica al codice, si verifica un problema nel passaggio 4, perché l'istanza della classe è stata eliminata definitivamente e il puntatore this non è più valido.With this change to the code, we run into a problem in step 4, because the class instance has been destroyed, and this is no longer valid. Non appena l'oggetto asincrono tenta di accedere alla variabile all'interno dell'istanza della classe, subirà un arresto anomalo (o eseguirà operazioni del tutto indefinite).As soon as the asynchronous object attempts to access the variable inside the class instance, it will crash (or do something entirely undefined).

La soluzione consiste nell'assegnare all'operazione asincrona (la coroutine) un riferimento sicuro proprio all'istanza della classe.The solution is to give the asynchronous operation—the coroutine—its own strong reference to the class instance. Così come è scritta, la coroutine mantiene in modo efficace un puntatore this non elaborato all'istanza della classe, ma ciò non è sufficiente per mantenere attiva l'istanza della classe.As currently written, the coroutine effectively holds a raw this pointer to the class instance; but that's not enough to keep the class instance alive.

Per mantenere attiva l'istanza della classe, modifica l'implementazione di RetrieveValueAsync come indicato di seguito.To keep the class instance alive, change the implementation of RetrieveValueAsync to that shown below.

IAsyncOperation<winrt::hstring> RetrieveValueAsync()
{
    auto strong_this{ get_strong() }; // Keep *this* alive.
    co_await 5s;
    co_return m_value;
}

Una classe C++/WinRT deriva direttamente o indirettamente dal modello winrt::implements.A C++/WinRT class directly or indirectly derives from the winrt::implements template. Per questo motivo, l'oggetto C++/WinRT può chiamare la funzione membro protetta implements::get_strong corrispondente per recuperare un riferimento sicuro al puntatore this.Because of that, the C++/WinRT object can call its implements::get_strong protected member function to retrieve a strong reference to its this pointer. Tieni presente che non è effettivamente necessario usare la variabile strong_this nell'esempio di codice precedente. È sufficiente chiamare get_strong per incrementare il conteggio dei riferimenti dell'oggetto C++/WinRT per mantenere valido il relativo puntatore this implicito.Note that there's no need to actually use the strong_this variable in the code example above; simply calling get_strong increments the C++/WinRT object's reference count, and keeps its implicit this pointer valid.

Importante

Dato che get_strong è una funzione membro del modello di struct winrt::implements, puoi chiamarla solo da una classe che deriva direttamente o indirettamente da winrt::implements, ad esempio una classe C++/WinRT.Because get_strong is a member function of the winrt::implements struct template, you can call it only from a class that directly or indirectly derives from winrt::implements, such as a C++/WinRT class. Per altre informazioni sulla derivazione da winrt::implements ed esempi, vedi Creare API con C++/WinRT.For more info about deriving from winrt::implements, and examples, see Author APIs with C++/WinRT.

Viene così risolto il problema che si è presentato in precedenza in corrispondenza del passaggio 4.This resolves the problem that we previously had when we got to step 4. Anche se scompaiono tutti gli altri riferimenti all'istanza della classe, la coroutine ha preso la precauzione di garantire che le relative dipendenze siano stabili.Even if all other references to the class instance disappear, the coroutine has taken the precaution of guaranteeing that its dependencies are stable.

Se un riferimento sicuro non è appropriato, puoi chiamare invece implements::get_weak per recuperare un riferimento debole a this.If a strong reference isn't appropriate, then you can instead call implements::get_weak to retrieve a weak reference to this. Conferma semplicemente che è possibile recuperare un riferimento sicuro prima di accedere a this.Just confirm that you can retrieve a strong reference before accessing this. Anche in questo caso get_weak è una funzione membro del modello di struct winrt::implements.Again, get_weak is a member function of the winrt::implements struct template.

IAsyncOperation<winrt::hstring> RetrieveValueAsync()
{
    auto weak_this{ get_weak() }; // Maybe keep *this* alive.

    co_await 5s;

    if (auto strong_this{ weak_this.get() })
    {
        co_return m_value;
    }
    else
    {
        co_return L"";
    }
}

Nell'esempio precedente il riferimento debole non impedisce l'eliminazione dell'istanza della classe quando non rimangono riferimenti sicuri,In the example above, the weak reference doesn't keep the class instance from being destroyed when no strong references remain. ma offre un modo per controllare se è possibile acquisire un riferimento sicuro prima dell'accesso alla variabile membro.But it gives you a way of checking whether a strong reference can be acquired before accessing the member variable.

Accesso sicuro al puntatore this con un delegato di gestione degli eventiSafely accessing the this pointer with an event-handling delegate

ScenarioThe scenario

Per informazioni generali sulla gestione degli eventi, vedi Gestire eventi mediante i delegati in C++/WinRT.For general info about event-handling, see Handle events by using delegates in C++/WinRT.

Nella sezione precedente sono stati evidenziati i potenziali problemi di durata a livello di coroutine e concorrenza.The previous section highlighted potential lifetime issues in the areas of coroutines and concurrency. Ma se gestisci un evento con una funzione membro di un oggetto o dall'interno di una funzione lambda all'interno della funzione membro di un oggetto, devi considerare le durate relative del destinatario dell'evento (l'oggetto che gestisce l'evento) e dell'origine dell'evento (l'oggetto che genera l'evento).But, if you handle an event with an object's member function, or from within a lambda function inside an object's member function, then you need to think about the relative lifetimes of the event recipient (the object handling the event) and the event source (the object raising the event). Esaminiamo alcuni esempi di codice.Let's look at some code examples.

Il listato di codice riportato di seguito definisce prima di tutto una semplice classe EventSource, che genera un evento generico che viene gestito da qualsiasi delegato aggiunto.The code listing below first defines a simple EventSource class, which raises a generic event that's handled by any delegates that have been added to it. Questo evento di esempio usa il tipo di delegato Windows::Foundation::EventHandler, ma i problemi e le soluzioni qui descritti si applicano a tutti i tipi di delegato.This example event happens to use the Windows::Foundation::EventHandler delegate type, but the issues and remedies here apply to any and all delegate types.

La classe EventRecipient fornisce poi un gestore per l'evento EventSource::Event sotto forma di una funzione lambda.Then, the EventRecipient class provides a handler for the EventSource::Event event in the form of a lambda function.

// pch.h
#pragma once
#include <iostream>
#include <winrt/Windows.Foundation.h>

// main.cpp : Defines the entry point for the console application.
#include "pch.h"

using namespace winrt;
using namespace Windows::Foundation;

struct EventSource
{
    winrt::event<EventHandler<int>> m_event;

    void Event(EventHandler<int> const& handler)
    {
        m_event.add(handler);
    }

    void RaiseEvent()
    {
        m_event(nullptr, 0);
    }
};

struct EventRecipient : winrt::implements<EventRecipient, IInspectable>
{
    winrt::hstring m_value{ L"Hello, World!" };

    void Register(EventSource& event_source)
    {
        event_source.Event([&](auto&& ...)
        {
            std::wcout << m_value.c_str() << std::endl;
        });
    }
};

int main()
{
    winrt::init_apartment();

    EventSource event_source;
    auto event_recipient{ winrt::make_self<EventRecipient>() };
    event_recipient->Register(event_source);
    event_source.RaiseEvent();
}

Il modello prevede che il destinatario di eventi disponga di un gestore di eventi lambda con dipendenze dal relativo puntatore this.The pattern is that the event recipient has a lambda event handler with dependencies on its this pointer. Ogni volta che il destinatario di eventi ha una durata superiore a quella dell'origine eventi, ha anche una durata superiore a tali dipendenzeWhenever the event recipient outlives the event source, it outlives those dependencies. e in questi casi, comuni, il modello funziona bene.And in those cases, which are common, the pattern works well. Alcuni di questi casi sono ovvi, come quando una pagina dell'interfaccia utente gestisce un evento generato da un controllo presente su tale pagina.Some of these cases are obvious, such as when a UI page handles an event raised by a control that's on the page. La pagina ha una durata superiore al pulsante, quindi anche il gestore ha una durata superiore al pulsante.The page outlives the button—so, the handler also outlives the button. Questo è vero ogni volta che il destinatario è il proprietario dell'origine (ad esempio come membro dati) oppure ogni volta che il destinatario e l'origine sono elementi di pari livello appartenenti direttamente a un altro oggetto.This holds true any time the recipient owns the source (as a data member, for example), or any time the recipient and the source are siblings and directly owned by some other object.

Quando sei certo di avere un caso in cui il gestore non avrà una durata superiore all'oggetto this da cui dipende, puoi acquisire this normalmente, senza prendere in considerazione la durata sicura o debole.When you're sure you have a case where the handler won't outlive the this that it depends on, then you can capture this normally, without consideration for strong or weak lifetime.

Tuttavia, esistono casi in cui la durata di this non andrà oltre il suo utilizzo in un gestore, inclusi i gestori per gli eventi di avanzamento e completamento generati da operazioni e azioni asincrone, ed è importante sapere come gestire questi casi.But there are still cases where this doesn't outlive its use in a handler (including handlers for completion and progress events raised by asynchronous actions and operations), and it's important to know how to deal with them.

  • Quando un'origine genera gli eventi in modo sincrono, puoi revocare il gestore e avere la certezza che non riceverai altri eventi.When an event source raises its events synchronously, you can revoke your handler and be confident that you won't receive any more events. Per gli eventi asincroni, tuttavia, anche dopo la revoca (e in particolare quando la revoca viene eseguita all'interno del distruttore), è possibile che un evento in corso raggiunga l'oggetto dopo l'avvio del processo di distruzione.But for asynchronous events, even after revoking (and especially when revoking within the destructor), an in-flight event might reach your object after it has started destructing. L'identificazione di una posizione in cui annullare la sottoscrizione prima della distruzione può attenuare il problema, ma continua la lettura di questo articolo per informazioni su una soluzione solida e affidabile.Finding a place to unsubscribe prior to destruction might mitigate the issue, but continue reading for a robust solution.
  • Se crei una coroutine per implementare un metodo asincrono, allora è possibile.If you're authoring a coroutine to implement an asynchronous method, then it's possible.
  • In rari casi, con alcuni oggetti del framework dell'interfaccia utente XAML (ad esempio SwapChainPanel), è possibile, se il destinatario viene finalizzato senza l'annullamento della registrazione dell'origine evento.In rare cases with certain XAML UI framework objects (SwapChainPanel, for example), then it's possible, if the recipient is finalized without unregistering from the event source.

ProblemaThe issue

La prossima versione della funzione main simula cosa accade quando viene eliminato definitivamente il destinatario di eventi (ad esempio quando esce dall'ambito) mentre l'origine eventi sta ancora generando eventi.This next version of the main function simulates what happens when the event recipient is destroyed (perhaps it goes out of scope) while the event source is still raising events.

int main()
{
    winrt::init_apartment();

    EventSource event_source;
    auto event_recipient{ winrt::make_self<EventRecipient>() };
    event_recipient->Register(event_source);
    event_recipient = nullptr; // Simulate the event recipient going out of scope.
    event_source.RaiseEvent(); // Behavior is now undefined within the lambda event handler; crashing is likely.
}

Il destinatario di eventi viene eliminato, ma il gestore di eventi lambda al suo interno è ancora incluso nella sottoscrizione dell'evento Event.The event recipient is destroyed, but the lambda event handler within it is still subscribed to the Event event. Quando viene generato tale evento, la funzione lambda tenta di dereferenziare il puntatore this, che a questo punto non è valido.When that event is raised, the lambda attempts to dereference the this pointer, which is at that point invalid. Ne risulta quindi una violazione di accesso dal codice nel gestore, o nella continuazione di una coroutine, che tenta di usarlo.So, an access violation results from code in the handler (or in a coroutine's continuation) attempting to use it.

Importante

Se si verifica una situazione di questo tipo, dovrai prendere in considerazione la durata dell'oggetto this e la possibilità che l'oggetto this acquisito abbia o meno una durata superiore all'acquisizione.If you encounter a situation like this, then you'll need to think about the lifetime of the this object; and whether or not the captured this object outlives the capture. In caso contrario, esegui l'acquisizione con un riferimento sicuro o debole, come verrà dimostrato di seguito.If it doesn't, then capture it with a strong or a weak reference, as we'll demonstrate below.

Se opportuno per il tuo scenario e se le considerazioni sul threading lo rendono possibile, un'altra opzione consiste nel revocare il gestore dopo che il destinatario avrà finito con l'evento o nel distruttore del destinatario.Or—if it makes sense for your scenario, and if threading considerations make it even possible—then another option is to revoke the handler after the recipient is done with the event, or in the recipient's destructor. Vedi Revocare un delegato registrato.See Revoke a registered delegate.

Ecco come si registra il gestore.This is how we're registering the handler.

event_source.Event([&](auto&& ...)
{
    std::wcout << m_value.c_str() << std::endl;
});

La funzione lambda acquisisce automaticamente tutte le variabili locali per riferimento.The lambda automatically captures any local variables by reference. Per questo esempio, quindi, si potrebbe scrivere questo codice equivalente.So, for this example, we could equivalently have written this.

event_source.Event([this](auto&& ...)
{
    std::wcout << m_value.c_str() << std::endl;
});

In entrambi i casi, si sta semplicemente acquisendo il puntatore this non elaborato.In both cases, we're just capturing the raw this pointer. Ciò non ha alcun effetto sul conteggio dei riferimenti, quindi niente impedisce l'eliminazione definitiva dell'oggetto corrente.And that has no effect on reference-counting, so nothing is preventing the current object from being destroyed.

SoluzioneThe solution

La soluzione consiste nell'acquisire un riferimento sicuro oppure, come si vedrà, un riferimento debole, se più appropriato.The solution is to capture a strong reference (or, as we'll see, a weak reference if that's more appropriate). Un riferimento sicuro incrementa il conteggio dei riferimenti e mantiene attivo l'oggetto corrente.A strong reference does increment the reference count, and it does keep the current object alive. È sufficiente dichiarare una variabile di acquisizione (chiamata strong_this in questo esempio) e inizializzarla con una chiamata a implements::get_strong, che recupera un riferimento sicuro al puntatore this.You just declare a capture variable (called strong_this in this example), and initialize it with a call to implements::get_strong, which retrieves a strong reference to our this pointer.

Importante

Dato che get_strong è una funzione membro del modello di struct winrt::implements, puoi chiamarla solo da una classe che deriva direttamente o indirettamente da winrt::implements, ad esempio una classe C++/WinRT.Because get_strong is a member function of the winrt::implements struct template, you can call it only from a class that directly or indirectly derives from winrt::implements, such as a C++/WinRT class. Per altre informazioni sulla derivazione da winrt::implements ed esempi, vedi Creare API con C++/WinRT.For more info about deriving from winrt::implements, and examples, see Author APIs with C++/WinRT.

event_source.Event([this, strong_this { get_strong()}](auto&& ...)
{
    std::wcout << m_value.c_str() << std::endl;
});

Puoi anche omettere l'acquisizione automatica dell'oggetto corrente e accedere al membro dati tramite la variabile di acquisizione anziché tramite il puntatore implicito this.You can even omit the automatic capture of the current object, and access the data member through the capture variable instead of via the implicit this.

event_source.Event([strong_this { get_strong()}](auto&& ...)
{
    std::wcout << strong_this->m_value.c_str() << std::endl;
});

Se un riferimento sicuro non è appropriato, puoi chiamare invece implements::get_weak per recuperare un riferimento debole a this.If a strong reference isn't appropriate, then you can instead call implements::get_weak to retrieve a weak reference to this. Un riferimento debole non mantiene attivo l'oggetto corrente.A weak reference does not keep the current object alive. Verifica quindi che sia comunque possibile recuperare un riferimento sicuro da quest'ultimo prima di accedere ai membri.So, just confirm that you can still retrieve a strong reference from the weak reference before accessing members.

event_source.Event([weak_this{ get_weak() }](auto&& ...)
{
    if (auto strong_this{ weak_this.get() })
    {
        std::wcout << strong_this->m_value.c_str() << std::endl;
    }
});

Se acquisisci un puntatore non elaborato, dovrai assicurarti che l'oggetto a cui punta venga mantenuto attivo.If you capture a raw pointer, then you'll need to make sure you keep the pointed-to object alive.

Se usi una funzione membro come delegatoIf you use a member function as a delegate

Oltre che alle funzioni lambda, questi principi si applicano anche all'uso di una funzione membro come delegato.As well as lambda functions, these principles also apply to using a member function as your delegate. La sintassi è diversa, quindi verrà presentato un esempio di codice.The syntax is different, so let's look at some code. Prima di tutto ecco il gestore eventi della funzione membro potenzialmente non sicuro che usa un puntatore this non elaborato.First, here's the potentially unsafe member function event handler, using a raw this pointer.

struct EventRecipient : winrt::implements<EventRecipient, IInspectable>
{
    winrt::hstring m_value{ L"Hello, World!" };

    void Register(EventSource& event_source)
    {
        event_source.Event({ this, &EventRecipient::OnEvent });
    }

    void OnEvent(IInspectable const& /* sender */, int /* args */)
    {
        std::wcout << m_value.c_str() << std::endl;
    }
};

Questo è il modo standard e convenzionale per fare riferimento a un oggetto e alla relativa funzione membro.This is the standard, conventional way to refer to an object and its member function. Per renderlo sicuro, a partire dalla versione 10.0.17763.0 (Windows 10, versione 1809) di Windows SDK puoi stabilire un riferimento sicuro o debole al momento della registrazione del gestore.To make this safe, you can—as of version 10.0.17763.0 (Windows 10, version 1809) of the Windows SDK—establish a strong or a weak reference at the point where the handler is registered. A questo punto, è noto che l'oggetto destinatario di eventi è ancora attivo.At that point, the event recipient object is known to be still alive.

Per un riferimento sicuro, è sufficiente chiamare get_strong al posto del puntatore this non elaborato.For a strong reference, just call get_strong in place of the raw this pointer. C++/WinRT garantisce che il delegato risultante mantenga un riferimento sicuro all'oggetto corrente.C++/WinRT ensures that the resulting delegate holds a strong reference to the current object.

event_source.Event({ get_strong(), &EventRecipient::OnEvent });

Se viene acquisito un riferimento sicuro, l'oggetto diventa idoneo per la distruzione solo dopo che è stata annullata la registrazione del gestore e sono stati restituiti tutti i callback in attesa.Capturing a strong reference means that your object will become eligible for destruction only after the handler has been unregistered and all outstanding callbacks have returned. Questa garanzia è tuttavia valida solo nel momento in cui viene generato l'evento.However, that guarantee is valid only at the time the event is raised. Se il gestore eventi è asincrono, dovrai assegnare alla coroutine un riferimento sicuro all'istanza della classe prima del primo punto di sospensione. Per informazioni dettagliate e codice, vedi la sezione precedente Accesso sicuro al puntatore this in una coroutine membro di classe in questo argomento.If your event handler is asynchronous, then you'll have to give your coroutine a strong reference to the class instance before the first suspension point (for details, and code, see the Safely accessing the this pointer in a class-member coroutine section earlier in this topic). Questa operazione ha tuttavia l'effetto di creare un riferimento circolare tra l'origine dell'evento e l'oggetto, che è quindi necessario interrompere in modo esplicito revocando l'evento.But that creates a circular reference between the event source and your object, so you need to explicitly break that by revoking your event.

Per un riferimento debole, chiama get_weak.For a weak reference, call get_weak. C++/WinRT garantisce che il delegato risultante mantenga un riferimento debole.C++/WinRT ensures that the resulting delegate holds a weak reference. All'ultimo minuto, e dietro le quinte, il delegato tenta di risolvere il riferimento debole in uno sicuro e chiama la funzione membro solo se riesce.At the last minute, and behind the scenes, the delegate attempts to resolve the weak reference to a strong one, and only calls the member function if it's successful.

event_source.Event({ get_weak(), &EventRecipient::OnEvent });

Se il delegato chiama la funzione membro, C++/WinRT mantiene attivo l'oggetto fino a quando il gestore non restituisce il controllo.If the delegate does call your member function, then C++/WinRT will keep your object alive until your handler returns. Se tuttavia il gestore è asincrono, il controllo viene restituito in corrispondenza dei punti di sospensione e dovrai quindi assegnare alla coroutine un riferimento sicuro all'istanza della classe prima del primo punto di sospensione.However, if your handler is asynchronous, then it returns at suspension points, and so you'll have to give your coroutine a strong reference to the class instance before the first suspension point. Anche in questo caso, per altre informazioni, vedi la sezione precedente Accesso sicuro al puntatore this in una coroutine membro di classe in questo argomento.Again, for more info, see Safely accessing the this pointer in a class-member coroutine section earlier in this topic.

Esempio di riferimento debole con SwapChainPanel::CompositionScaleChangedA weak reference example using SwapChainPanel::CompositionScaleChanged

In questo esempio di codice viene usato l'evento SwapChainPanel::CompositionScaleChanged per continuare con la presentazione dei riferimenti deboli.In this code example, we use the SwapChainPanel::CompositionScaleChanged event by way of another illustration of weak references. Il codice registra un gestore eventi usando una funzione lambda che acquisisce un riferimento debole al destinatario.The code registers an event handler using a lambda that captures a weak reference to the recipient.

winrt::Windows::UI::Xaml::Controls::SwapChainPanel m_swapChainPanel;
winrt::event_token m_compositionScaleChangedEventToken;

void RegisterEventHandler()
{
    m_compositionScaleChangedEventToken = m_swapChainPanel.CompositionScaleChanged([weak_this{ get_weak() }]
        (Windows::UI::Xaml::Controls::SwapChainPanel const& sender,
        Windows::Foundation::IInspectable const& object)
    {
        if (auto strong_this{ weak_this.get() })
        {
            strong_this->OnCompositionScaleChanged(sender, object);
        }
    });
}

void OnCompositionScaleChanged(Windows::UI::Xaml::Controls::SwapChainPanel const& sender,
    Windows::Foundation::IInspectable const& object)
{
    // Here, we know that the "this" object is valid.
}

Nella clausola di acquisizione lamba viene creata una variabile temporanea, che rappresenta un riferimento debole a this.In the lamba capture clause, a temporary variable is created, representing a weak reference to this. Nel corpo della funzione lambda, se è possibile ottenere un riferimento sicuro a this, viene chiamata la funzione OnCompositionScaleChanged.In the body of the lambda, if a strong reference to this can be obtained, then the OnCompositionScaleChanged function is called. In questo modo, in OnCompositionScaleChanged è possibile usare this in modo sicuro.That way, inside OnCompositionScaleChanged, this can safely be used.

Riferimenti deboli in C++/WinRTWeak references in C++/WinRT

In precedenza è stato illustrato l'uso dei riferimenti deboli.Above, we saw weak references being used. In generale, sono utili per interrompere i riferimenti ciclici.In general, they're good for breaking cyclic references. Ad esempio, per quanto riguarda l'implementazione nativa del framework dell'interfaccia utente basata su XAML, a causa della progettazione pregressa di tale framework il meccanismo del riferimento debole in C++/WinRT è necessario per gestire i riferimenti ciclici.For example, for the native implementation of the XAML-based UI framework—because of the historical design of the framework—the weak reference mechanism in C++/WinRT is necessary to handle cyclic references. Al di fuori di XAML, tuttavia, probabilmente non avrai la necessità di usare riferimenti deboli, anche se in effetti non hanno alcuna caratteristica intrinsecamente specifica di XAML.Outside of XAML, though, you likely won't need to use weak references (not that there's anything inherently XAML-specific about them). Dovresti spesso essere in grado di progettare le tue API C++/WinRT in modo da evitare la necessità di riferimenti ciclici e deboli.Rather you should, more often than not, be able to design your own C++/WinRT APIs in such a way as to avoid the need for cyclic references and weak references.

Per ogni tipo di dato che dichiari, non è immediatamente evidente per C++/WinRT se o quando sono necessari i riferimenti deboli.For any given type that you declare, it's not immediately obvious to C++/WinRT whether or when weak references are needed. Pertanto, C++/WinRT offre automaticamente il supporto dei riferimenti deboli nel modello di struct winrt::implements, da cui derivano direttamente o indirettamente i tuoi tipi C++/WinRT.So, C++/WinRT provides weak reference support automatically on the struct template winrt::implements, from which your own C++/WinRT types directly or indirectly derive. È di tipo "pay-for-play", ovvero non ha alcun costo a meno che non venga effettivamente eseguita la query all'oggetto per IWeakReferenceSource.It's pay-for-play, in that it doesn't cost you anything unless your object is actually queried for IWeakReferenceSource. E puoi scegliere di rifiutare esplicitamente tale supporto.And you can choose explicitly to opt out of that support.

Esempi di codiceCode examples

Il modello di struct winrt::weak_ref è un'opzione per ottenere un riferimento debole a un'istanza di classe.The winrt::weak_ref struct template is one option for getting a weak reference to a class instance.

Class c;
winrt::weak_ref<Class> weak{ c };

In alternativa, puoi usare la funzione helper winrt::make_weak.Or, you can use the use the winrt::make_weak helper function.

Class c;
auto weak = winrt::make_weak(c);

La creazione di un riferimento debole non influenza il conteggio dei riferimenti per l'oggetto stesso, ma causa semplicemente l'allocazione di un blocco di controllo.Creating a weak reference doesn't affect the reference count on the object itself; it just causes a control block to be allocated. Il blocco di controllo si occupa dell'implementazione della semantica di riferimento debole.That control block takes care of implementing the weak reference semantics. Puoi quindi provare a promuovere il riferimento debole in un riferimento sicuro e, se ha esito positivo, usarlo.You can then try to promote the weak reference to a strong reference and, if successful, use it.

if (Class strong = weak.get())
{
    // use strong, for example strong.DoWork();
}

A condizione che esista ancora un altro riferimento sicuro, la chiamata di weak_ref::get incrementa il conteggio dei riferimenti e restituisce il riferimento sicuro al chiamante.Provided that some other strong reference still exists, the weak_ref::get call increments the reference count and returns the strong reference to the caller.

Rifiuto esplicito del supporto di riferimenti deboliOpting out of weak reference support

Il supporto di riferimenti deboli è automatico.Weak reference support is automatic. Puoi però scegliere di rifiutare esplicitamente tale supporto passando lo struct indicatore winrt::no_weak_ref come argomento del modello per la classe base.But you can choose explicitly to opt out of that support by passing the winrt::no_weak_ref marker struct as a template argument to your base class.

Se la classe deriva direttamente da winrt::implements.If you derive directly from winrt::implements.

struct MyImplementation: implements<MyImplementation, IStringable, no_weak_ref>
{
    ...
}

Se si sta creando una classe di runtime.If you're authoring a runtime class.

struct MyRuntimeClass: MyRuntimeClassT<MyRuntimeClass, no_weak_ref>
{
    ...
}

La posizione in cui appare l'elemento struct dell'indicatore nel pacchetto di parametri variadic non ha importanza.It doesn't matter where in the variadic parameter pack the marker struct appears. Se richiedi un riferimento debole per un tipo rifiutato esplicitamente, il compilatore ti aiuterà visualizzando il messaggio "This is only for weak ref support" (Questo è solo per il supporto di riferimento debole).If you request a weak reference for an opted-out type, then the compiler will help you out with "This is only for weak ref support".

API importantiImportant APIs