Références fortes et faibles en C++/WinRTStrong and weak references in C++/WinRT

L’environnement Windows Runtime est un système avec décompte des références. Dans un tel système, il est important de connaître la signification des références fortes et faibles (ainsi que des références qui ne sont ni fortes ni faibles, comme le pointeur implicite this), et de faire la distinction entre elles.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). Comme vous le verrez dans cette rubrique, le fait de savoir gérer ces références correctement est indispensable pour bénéficier d’un système fiable qui s’exécute correctement et éviter les plantages imprévisibles.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. En fournissant des fonctions d’assistance qui prennent entièrement en charge la projection de langage, C++/WinRT fait la moitié du travail nécessaire à la création de systèmes plus complexes, simplement et correctement.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.

Accès sécurisé au pointeur this dans une coroutine de membre de classeSafely accessing the this pointer in a class-member coroutine

Pour plus d’informations sur les coroutines et les exemples de code, consultez Opérations concurrentes et asynchrones avec C++/WinRT.For more info about coroutines, and code examples, see Concurrency and asynchronous operations with C++/WinRT.

Le listing de code ci-dessous montre un exemple typique d’une coroutine, qui est une fonction membre d’une classe.The code listing below shows a typical example of a coroutine that's a member function of a class. Vous pouvez copier-coller cet exemple dans les fichiers spécifiés, dans un nouveau projet Windows Console Application (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 s’exécute pendant un certain temps, puis finit par retourner une copie du membre de données MyClass::m_value.MyClass::RetrieveValueAsync spends some time working, and eventually it returns a copy of the MyClass::m_value data member. L’appel de RetrieveValueAsync entraîne la création d’un objet asynchrone, et cet objet a un pointeur implicite this (par le biais duquel m_value devient accessible).Calling RetrieveValueAsync causes an asynchronous object to be created, and that object has an implicit this pointer (through which, eventually, m_value is accessed).

N’oubliez pas que, dans une coroutine, l’exécution est synchrone jusqu’au premier point d’interruption, où le contrôle est retourné à l’appelant.Remember that, in a coroutine, execution is synchronous up until the first suspension point, where control is returned to the caller. Dans RetrieveValueAsync, le premier co_await est le premier point d’interruption.In RetrieveValueAsync, the first co_await is the first suspension point. En attendant que la coroutine reprenne son exécution (environ cinq secondes plus tard, dans ce cas), tout peut arriver au pointeur implicite this par lequel nous accédons à 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.

Voici la séquence complète des événements.Here's the full sequence of events.

  1. Dans main, une instance de MyClass est créée (myclass_instance).In main, an instance of MyClass is created (myclass_instance).
  2. L’objet async est créé, et pointe vers myclass_instance (via son pointeur this).The async object is created, pointing (via its this) to myclass_instance.
  3. La fonction winrt::Windows::Foundation::IAsyncAction::get atteint son premier point d’interruption, se bloque pendant quelques secondes, puis retourne le résultat de 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 retourne la valeur de this->m_value.RetrieveValueAsync returns the value of this->m_value.

L’étape 4 est sécurisée tant que this reste valide.Step 4 is safe only as long as this remains valid.

Mais que se passe-t-il si l’instance de classe est détruite avant la fin de l’opération asynchrone ?But what if the class instance is destroyed before the async operation completes? L’instance de classe peut devenir hors de portée avant la fin de la méthode asynchrone pour de nombreuses raisons.There are all kinds of ways the class instance could go out of scope before the asynchronous method has completed. Toutefois, nous pouvons le simuler en affectant nullptr à l’instance de classe.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;
}

Après la destruction de l’instance de classe, il semble que nous ne le référençons plus directement.After the point where we destroy the class instance, it looks like we don't directly refer to it again. Bien entendu, l’objet asynchrone est pointé par un pointeur this, et tente de l’utiliser pour copier la valeur stockée dans l’instance de 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. La coroutine est une fonction membre, et s’attend naturellement à pouvoir utiliser son pointeur this.The coroutine is a member function, and it expects to be able to use its this pointer with impunity.

Après cette modification du code, nous rencontrons un problème à l’étape 4, car l’instance de classe a été détruite, et que le pointeur this n’est plus valide.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. Dès que l’objet asynchrone tente d’accéder à la variable à l’intérieur de l’instance de classe, il plante (ou fait quelque chose de totalement inattendu).As soon as the asynchronous object attempts to access the variable inside the class instance, it will crash (or do something entirely undefined).

La solution consiste à donner à l’opération asynchrone (la coroutine) sa propre référence forte à l’instance de classe.The solution is to give the asynchronous operation—the coroutine—its own strong reference to the class instance. Telle qu’elle est écrite, la coroutine contient effectivement un pointeur this brut qui pointe vers l’instance de classe. Toutefois, cela ne suffit pas à préserver l’instance de 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.

Pour préserver l’instance de classe, remplacez l’implémentation de RetrieveValueAsync par celle illustrée ci-dessous.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;
}

Une classe C++/WinRT dérive directement ou indirectement d’un modèle winrt::implements.A C++/WinRT class directly or indirectly derives from the winrt::implements template. Pour cette raison, l’objet C++/WinRT peut appeler sa fonction membre protégée implements::get_strong pour récupérer une référence forte à son pointeur 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. Notez qu’il est inutile d’utiliser la variable strong_this dans l’exemple de code ci-dessus. Il vous suffit d’appeler get_strong pour incrémenter le nombre de références de l’objet C++/WinRT et pour que son pointeur implicite this reste valide.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.

Important

Étant donné que get_strong est une fonction membre du modèle struct winrt::implements, vous pouvez l’appeler uniquement à partir d’une classe qui dérive directement ou indirectement de winrt::implements, comme la 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. Pour plus d’informations sur la dérivation à partir de winrt::implements, et pour obtenir des exemples, consultez Créer des API avec C++/WinRT.For more info about deriving from winrt::implements, and examples, see Author APIs with C++/WinRT.

Cela résout le problème que nous avons eu à l’étape 4.This resolves the problem that we previously had when we got to step 4. Même si toutes les autres références à l’instance de classe disparaissent, la coroutine a pris la précaution de garantir la stabilité de ses dépendances.Even if all other references to the class instance disappear, the coroutine has taken the precaution of guaranteeing that its dependencies are stable.

Si une référence forte ne convient pas, vous pouvez appeler à la place implements::get_weak pour récupérer une référence faible au pointeur this.If a strong reference isn't appropriate, then you can instead call implements::get_weak to retrieve a weak reference to this. Vérifiez simplement que vous pouvez récupérer une référence forte avant d’accéder au pointeur this.Just confirm that you can retrieve a strong reference before accessing this. Là encore, get_weak est une fonction membre du modèle de 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"";
    }
}

Dans l’exemple ci-dessus, la référence faible n’empêche pas la destruction de l’instance de classe lorsqu’il ne reste plus aucune référence forte.In the example above, the weak reference doesn't keep the class instance from being destroyed when no strong references remain. Toutefois, elle vous permet de vérifier si une référence forte peut être acquise avant d’accéder à la variable membre.But it gives you a way of checking whether a strong reference can be acquired before accessing the member variable.

Accès sécurisé au pointeur this avec un délégué de gestion des événements.Safely accessing the this pointer with an event-handling delegate

ScénarioThe scenario

Pour obtenir des informations générales sur la gestion des événements, consultez Gérer des événements en utilisant des délégués en C++/WinRT.For general info about event-handling, see Handle events by using delegates in C++/WinRT.

La section précédente a abordé des problèmes potentiels de durée de vie au niveau des coroutines et de l’accès concurrentiel.The previous section highlighted potential lifetime issues in the areas of coroutines and concurrency. Toutefois, si vous gérez un événement avec la fonction membre d’un objet, ou à partir d’une fonction lambda au sein de la fonction membre d’un objet, vous devez penser aux durées de vie relatives du destinataire d’événement (l’objet qui gère l’événement) et de la source d’événement (l’objet qui déclenche l’événement).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). Voyons quelques exemples de code.Let's look at some code examples.

Le listing de code ci-dessous définit d’abord une simple classe EventSource, ce qui déclenche un événement générique qui est géré par des délégués qui lui ont été ajoutés.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. Cet exemple d’événement se produit pour utiliser le type délégué Windows::Foundation::EventHandler. Cependant, les problèmes et les solutions mentionnés ici s’appliquent à tous les types délégués.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.

Ensuite, la classe EventRecipient fournit un gestionnaire pour l’événement EventSource::Event sous la forme d’une fonction 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();
}

Dans ce modèle, le destinataire de l’événement a un gestionnaire d’événements lambda avec des dépendances à son pointeur this.The pattern is that the event recipient has a lambda event handler with dependencies on its this pointer. Chaque fois que la durée de vie du destinataire de l’événement dépasse celle de la source d’événements, elle dépasse également celle de ces dépendances.Whenever the event recipient outlives the event source, it outlives those dependencies. Et, dans ce cas (qui est courant), le modèle fonctionne bien.And in those cases, which are common, the pattern works well. Certains de ces cas sont évidents, par exemple, lorsqu’une page d’interface utilisateur gère un événement déclenché par un contrôle qui se trouve dans la page.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 durée de vie de la page dépasse celle du bouton, par conséquent, la durée de vie du gestionnaire dépasse également celle du bouton.The page outlives the button—so, the handler also outlives the button. Ceci est vrai chaque fois que le destinataire est le propriétaire de la source (comme membre de données, par exemple), ou chaque fois que le destinataire et la source sont frère/sœur et appartiennent directement à un autre objet.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.

Quand il existe un cas où vous êtes certain que le gestionnaire ne survivra pas au this dont il dépend, vous pouvez capturer this normalement, sans tenir compte de la durée de vie forte ou faible.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.

Il existe cependant des cas où this ne survit pas à son utilisation dans un gestionnaire (y compris les gestionnaires d’événements de progression et d’achèvement déclenchés par des actions et opérations asynchrones), et il est important de savoir comment les gérer.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.

  • Quand une source d’événements déclenche ses événements de façon synchrone, vous pouvez révoquer votre gestionnaire pour être sûr de ne plus recevoir d’événements.When an event source raises its events synchronously, you can revoke your handler and be confident that you won't receive any more events. Toutefois, pour les événements asynchrones, un événement en cours peut atteindre votre objet une fois qu’il a commencé sa destruction, même après une révocation (en particulier lorsque vous révoquez au sein du destructeur).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. Vous pouvez atténuer le problème en recherchant un emplacement pour vous désabonner avant de commencer la destruction, mais vous découvrirez une solution robuste en continuant de lire cet article.Finding a place to unsubscribe prior to destruction might mitigate the issue, but continue reading for a robust solution.
  • Si vous créez une coroutine pour implémenter une méthode asynchrone, alors cela est possible.If you're authoring a coroutine to implement an asynchronous method, then it's possible.
  • Dans de rares cas, avec certains objets du framework de l’interface utilisateur XAML (SwapChainPanel, par exemple), cela est possible, si le destinataire est finalisé sans désinscription de la source d’événement.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.

Le problèmeThe issue

La version suivante de la fonction main simule ce qui se passe lorsque le destinataire d’événement est détruit (par exemple, s’il devient hors de portée) alors que la source d’événements continue de déclencher des événements.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.
}

Le destinataire de l’événement est détruit, mais le gestionnaire d’événements lambda qu’il contient est encore abonné à l’événement Event.The event recipient is destroyed, but the lambda event handler within it is still subscribed to the Event event. Lorsque cet événement est déclenché, l’expression lambda tente de déréférencer le pointeur this, qui à ce stade, n’est plus valide.When that event is raised, the lambda attempts to dereference the this pointer, which is at that point invalid. Par conséquent, une violation d’accès se produit après que le code du gestionnaire (ou de la continuation d’une coroutine) a tenté de l’utiliser.So, an access violation results from code in the handler (or in a coroutine's continuation) attempting to use it.

Important

Si vous rencontrez une situation similaire, vous devrez réfléchir à la durée de vie de l’objet this et décider si l’objet this capturé doit survivre à la capture.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. Si ce n’est pas le cas, capturez-le avec une référence forte ou faible, comme nous allons l’expliquer ci-dessous.If it doesn't, then capture it with a strong or a weak reference, as we'll demonstrate below.

Ou, si cela a du sens pour votre scénario et si les considérations liées au thread le rendent même possible, alors une autre option consiste à révoquer le gestionnaire une fois que le destinataire en a terminé avec l’événement, ou dans le destructeur du destinataire.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. Consultez Révoquer un délégué inscrit.See Revoke a registered delegate.

Voici comment nous allons inscrire le gestionnaire.This is how we're registering the handler.

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

L’expression lambda capture automatiquement toutes les variables locales par référence.The lambda automatically captures any local variables by reference. Par conséquent, pour cet exemple, nous pourrions avoir écrit cela.So, for this example, we could equivalently have written this.

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

Dans les deux cas, nous allons simplement capturer le pointeur this brut.In both cases, we're just capturing the raw this pointer. Cela n’a aucun effet sur le comptage de références, donc rien n’empêche l’objet actuel d’être détruit.And that has no effect on reference-counting, so nothing is preventing the current object from being destroyed.

La solutionThe solution

La solution consiste à capturer une référence forte (ou, comme nous le verrons, une référence faible si celle-ci est plus appropriée).The solution is to capture a strong reference (or, as we'll see, a weak reference if that's more appropriate). Une référence forte incrémente le nombre de références et préserve l’objet actuel.A strong reference does increment the reference count, and it does keep the current object alive. Il vous suffit de déclarer une variable de capture (appelée strong_this dans cet exemple) et de l’initialiser avec un appel à implements::get_strong, qui récupère une référence forte à notre pointeur 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.

Important

Étant donné que get_strong est une fonction membre du modèle struct winrt::implements, vous pouvez l’appeler uniquement à partir d’une classe qui dérive directement ou indirectement de winrt::implements, comme la 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. Pour plus d’informations sur la dérivation à partir de winrt::implements, et pour obtenir des exemples, consultez Créer des API avec 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;
});

Vous pouvez même omettre la capture automatique de l’objet actuel et accéder au membre de données via la variable de capture, au lieu de passer par le pointeur implicite 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;
});

Si une référence forte ne convient pas, vous pouvez appeler à la place implements::get_weak pour récupérer une référence faible au pointeur this.If a strong reference isn't appropriate, then you can instead call implements::get_weak to retrieve a weak reference to this. Une référence faible ne maintient pas l’activité de l’objet actuel.A weak reference does not keep the current object alive. Vérifiez donc simplement que vous pouvez toujours récupérer une référence forte à partir de la référence faible avant d’accéder aux membres.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;
    }
});

Si vous capturez un pointeur brut, vous devrez vous assurer de maintenir l’objet pointé actif.If you capture a raw pointer, then you'll need to make sure you keep the pointed-to object alive.

Si vous utilisez une fonction membre comme déléguéIf you use a member function as a delegate

Tout comme les fonctions lambda, ces principes s’appliquent à l’utilisation d’une fonction membre comme délégué.As well as lambda functions, these principles also apply to using a member function as your delegate. La syntaxe est toutefois différente, nous allons donc regarder à quoi ressemble le code.The syntax is different, so let's look at some code. Tout d’abord, voici le gestionnaire d’événements des fonctions membres potentiellement dangereuses, qui utilise un pointeur this brut.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;
    }
};

Il s’agit de la méthode conventionnelle pour référencer un objet et sa fonction membre.This is the standard, conventional way to refer to an object and its member function. Pour rendre ce scénario sécurisé, vous pouvez (à compter de la version 10.0.17763.0 du SDK Windows, Windows 10, version 1809) créer une référence forte ou faible au point où le gestionnaire est inscrit.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. À ce stade, l’objet de destinataire d’événement est encore en vie.At that point, the event recipient object is known to be still alive.

Pour une référence forte, appelez get_strong au lieu du pointeur this brut.For a strong reference, just call get_strong in place of the raw this pointer. C++/WinRT garantit que le délégué résultant conserve une référence forte à l’objet actuel.C++/WinRT ensures that the resulting delegate holds a strong reference to the current object.

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

Capturer une référence forte signifie que votre objet deviendra éligible pour la destruction uniquement lorsque l’inscription du gestionnaire sera annulée et que tous les rappels en attente seront retournés.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. Toutefois, cette garantie n’est valide qu’au moment où l’événement est déclenché.However, that guarantee is valid only at the time the event is raised. Si votre gestionnaire d’événements est asynchrone, vous devrez fournir à votre coroutine une référence forte à l’instance de classe avant le premier point d’interruption (pour obtenir plus d’informations et du code, consultez la section Accès sécurisé au pointeur this dans une coroutine de membre de classe plus haut dans cette rubrique).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). Cependant, cela crée une référence circulaire entre la source d’événement et votre objet que vous devez donc rompre explicitement en révoquant votre événement.But that creates a circular reference between the event source and your object, so you need to explicitly break that by revoking your event.

Pour une référence faible, appelez get_weak.For a weak reference, call get_weak. C++/WinRT garantit que le délégué résultant conserve une référence faible.C++/WinRT ensures that the resulting delegate holds a weak reference. À la dernière minute, et en arrière-plan, le délégué tente de résoudre la référence faible en une référence forte, et n’appelle la fonction membre qu’en cas de réussite.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 });

Si le délégué appelle votre fonction membre, C++/WinRT maintiendra votre objet actif jusqu’au retour de votre gestionnaire.If the delegate does call your member function, then C++/WinRT will keep your object alive until your handler returns. Toutefois, votre gestionnaire retourne aux points d’interruption s’il est asynchrone. Vous devez donc fournir à votre coroutine une référence forte à l’instance de classe avant le premier point d’interruption.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. Une fois de plus, pour plus d’informations, consultez la section Accès sécurisé au pointeur this dans une coroutine de membre de classe plus haut dans cette rubrique.Again, for more info, see Safely accessing the this pointer in a class-member coroutine section earlier in this topic.

Exemple de référence faible utilisant SwapChainPanel::CompositionScaleChangedA weak reference example using SwapChainPanel::CompositionScaleChanged

Dans cet exemple de code, nous utilisons l’événement SwapChainPanel::CompositionScaleChanged par le biais d’une autre illustration de références faibles.In this code example, we use the SwapChainPanel::CompositionScaleChanged event by way of another illustration of weak references. Le code inscrit un gestionnaire d’événements utilisant une expression lambda qui capture une référence faible au destinataire.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.
}

Dans la clause de capture lamba, une variable temporaire est créée et représente une référence faible à this.In the lamba capture clause, a temporary variable is created, representing a weak reference to this. Dans le corps de l’expression lambda, si une référence forte à this peut être obtenue, alors la fonction OnCompositionScaleChanged est appelée.In the body of the lambda, if a strong reference to this can be obtained, then the OnCompositionScaleChanged function is called. De cette façon, à l’intérieur de OnCompositionScaleChanged, this peut être utilisé de façon sécurisée.That way, inside OnCompositionScaleChanged, this can safely be used.

Références faibles en C++/WinRTWeak references in C++/WinRT

Nous avons vu plus haut comment utiliser des références faibles.Above, we saw weak references being used. En général, elles conviennent bien à la suppression des références cycliques.In general, they're good for breaking cyclic references. Par exemple, pour l’implémentation native du framework d’interface utilisateur basé sur XAML (en raison de la conception historique du framework), le mécanisme de références faibles en C++/WinRT est nécessaire pour gérer les références cycliques.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. En dehors du XAML, cependant, vous n’aurez probablement pas à utiliser des références faibles (même si celles-ci ne sont pas liées intrinsèquement au XAML).Outside of XAML, though, you likely won't need to use weak references (not that there's anything inherently XAML-specific about them). Vous devriez pouvoir, le plus souvent, concevoir vos propres API C++/WinRT de manière à éviter le besoin de références cycliques et de références faibles.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.

Pour n’importe quel type donné que vous déclarez, il n’est pas immédiatement évident pour C++/WinRT de déterminer si des références faibles sont nécessaires.For any given type that you declare, it's not immediately obvious to C++/WinRT whether or when weak references are needed. Par conséquent, C++/WinRT fournit la prise en charge des références faibles automatiquement sur le modèle de struct winrt::implements, à partir duquel vos propres types C++/WinRT dérivent directement ou indirectement.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. Il s’agit d’une prise en charge de type « pay-for-play », dans la mesure où elle ne vous coûte rien, sauf si votre objet est réellement interrogé pour IWeakReferenceSource.It's pay-for-play, in that it doesn't cost you anything unless your object is actually queried for IWeakReferenceSource. Et vous pouvez choisir explicitement de refuser cette prise en charge.And you can choose explicitly to opt out of that support.

Exemples de codeCode examples

Le modèle de struct winrt::weak_ref permet d’obtenir une référence faible à une instance de 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 };

Vous pouvez également utiliser la fonction d’assistance winrt::make_weak.Or, you can use the use the winrt::make_weak helper function.

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

La création d’une référence faible n’affecte pas le nombre de références de l’objet. Elle entraîne simplement l’allocation d’un bloc de contrôle.Creating a weak reference doesn't affect the reference count on the object itself; it just causes a control block to be allocated. Ce bloc de contrôle s’occupe de l’implémentation de la sémantique des références faibles.That control block takes care of implementing the weak reference semantics. Vous pouvez essayer de promouvoir la référence faible en une référence forte et, en cas de réussite, l’utiliser.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();
}

À condition qu’une autre référence forte existe toujours, l’appel weak_ref::get incrémente le nombre de références et retourne la référence forte à l’appelant.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.

Refus de la prise en charge des références faiblesOpting out of weak reference support

La prise en charge des références faibles est automatique.Weak reference support is automatic. Mais vous pouvez choisir explicitement de refuser cette prise en charge en passant le struct de marqueur winrt::no_weak_ref en tant qu’argument de modèle à votre classe de 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.

Si vous dérivez directement de winrt::implements.If you derive directly from winrt::implements.

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

Si vous créez une classe Runtime.If you're authoring a runtime class.

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

Peu importe où le struct de marqueur apparaît dans le pack de paramètre variadique.It doesn't matter where in the variadic parameter pack the marker struct appears. Si vous demandez une référence faible pour un type refusé, le compilateur vous aidera en affichant une erreur « This is only for weak ref support » (Ceci est réservé à la prise en charge des références faibles).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 importantesImportant APIs