Starke und schwache Verweise in C++/WinRTStrong and weak references in C++/WinRT

Windows-Runtime ist ein System mit Verweiszählung. Es ist wichtig, dass Sie mit der Bedeutung und dem Unterschied zwischen starken und schwachen Verweisen vertraut sind (und mit Verweisen, die keins von beidem sind, wie etwa der implizite this-Zeiger).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). Wie Sie in diesem Thema erfahren werden, kann das Wissen um den korrekten Umgang mit diesen Verweisen den Unterschied zwischen einem zuverlässigen System bedeuten, das reibungslos läuft, und einem, das zu unvorhersehbaren Abstürzen neigt.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. Durch die Bereitstellung von Hilfsfunktionen mit tiefgreifender Unterstützung in der Sprachprojektion kommt Ihnen C++/WinRT bei Ihrer Arbeit, komplexere Systeme einfach und ordnungsgemäß aufzubauen, auf halbem Weg entgegen.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.

Sicherer Zugriff auf den this-Zeiger in einer Klassenmember-CoroutineSafely accessing the this pointer in a class-member coroutine

Weitere Informationen zu Coroutinen und Codebeispiele finden Sie unter Parallelität und asynchrone Vorgänge mit C++/WinRT.For more info about coroutines, and code examples, see Concurrency and asynchronous operations with C++/WinRT.

Die Codeauflistung unten zeigt ein typisches Beispiel einer Coroutine, die eine Memberfunktion einer Klasse ist.The code listing below shows a typical example of a coroutine that's a member function of a class. Sie können dieses Beispiel kopieren und es in einem neuen Windows-Konsolenanwendung (C++/WinRT)-Projekt in die angegebenen Dateien einfügen.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 arbeitet eine Zeit lang und gibt schließlich eine Kopie des MyClass::m_value-Datenmembers zurück.MyClass::RetrieveValueAsync spends some time working, and eventually it returns a copy of the MyClass::m_value data member. Das Aufrufen von RetrieveValueAsync bewirkt die Erstellung eines asynchronen Objekts, und dieses Objekt verfügt über einen impliziten this-Zeiger (über den schließlich der Zugriff auf m_value erfolgt).Calling RetrieveValueAsync causes an asynchronous object to be created, and that object has an implicit this pointer (through which, eventually, m_value is accessed).

Beachten Sie, dass in einer Coroutine die Ausführung bis zum ersten Anhaltepunkt, an dem die Steuerung an den Aufrufer zurückgegeben wird, synchron verläuft.Remember that, in a coroutine, execution is synchronous up until the first suspension point, where control is returned to the caller. In RetrieveValueAsync ist das erste co_await der erste Anhaltepunkt.In RetrieveValueAsync, the first co_await is the first suspension point. Bis die Coroutine fortgesetzt wird (in diesem Fall ca. 5 Sekunden später), kann mit dem impliziten this-Zeiger, über den der Zugriff auf m_value erfolgt, alles Mögliche geschehen sein.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.

Hier sehen Sie die vollständige Abfolge der Ereignisse.Here's the full sequence of events.

  1. In main wird eine Instanz von MyClass erstellt (myclass_instance).In main, an instance of MyClass is created (myclass_instance).
  2. Das async-Objekt wird erstellt und verweist (mithilfe seines this) auf myclass_instance.The async object is created, pointing (via its this) to myclass_instance.
  3. Die Funktion winrt::Windows::Foundation::IAsyncAction::get trifft auf den ersten Anhaltepunkt, wird einige Sekunden lang blockiert und gibt dann das Ergebnis von RetrieveValueAsync zurück.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 gibt den Wert von this->m_value zurück.RetrieveValueAsync returns the value of this->m_value.

Schritt 4 ist nur unbedenklich, solange this gültig bleibt.Step 4 is safe only as long as this remains valid.

Was aber, wenn die Klasseninstanz zerstört wird, bevor der asynchrone Vorgang abgeschlossen wird?But what if the class instance is destroyed before the async operation completes? Die Klasseninstanz kann auf alle möglichen Weise aus ihrem Bereich gelangen, bevor die asynchrone Methode abgeschlossen ist.There are all kinds of ways the class instance could go out of scope before the asynchronous method has completed. Wir können das aber simulieren, indem wir die Klasseninstanz auf nullptr festlegen.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;
}

Nach dem Punkt, an dem wir die Klasseninstanz zerstören, sieht es so aus, als ob wir nicht direkt auf sie verweisen würden.After the point where we destroy the class instance, it looks like we don't directly refer to it again. Aber natürlich weist das asynchrone Objekt einen this-Zeiger auf und versucht, diesen zu verwenden, um den in der Klasseninstanz gespeicherten Wert zu kopieren.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. Die Coroutine ist eine Memberfunktion und erwartet, ihren this-Zeiger straffrei verwenden zu können.The coroutine is a member function, and it expects to be able to use its this pointer with impunity.

Durch diese Änderung am Code bekommen wir in Schritt 4 ein Problem, da die Klasseninstanz zerstört wurde und this nicht mehr gültig ist.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. Sobald das asynchrone Objekt versucht, auf die Variable innerhalb der Klasseninstanz zuzugreifen, stürzt es ab (oder tut etwas völlig Undefiniertes).As soon as the asynchronous object attempts to access the variable inside the class instance, it will crash (or do something entirely undefined).

Die Lösung besteht darin, den asynchronen Vorgang — die Coroutine — mit einem eigenen starken Verweis auf die Klasseninstanz auszustatten.The solution is to give the asynchronous operation—the coroutine—its own strong reference to the class instance. So, wie sie aktuell geschrieben ist, enthält die Coroutine effektiv einen nackten this-Zeiger auf die Klasseninstanz, das ist aber nicht ausreichend, um die Klasseninstanz beizubehalten.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.

Um die Klasseninstanz beizubehalten, ändern Sie die Implementierung von RetrieveValueAsync in die unten abgebildete.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;
}

Eine C++/WinRT-Klasse leitet sich direkt oder indirekt aus der Vorlage winrt::implements ab.A C++/WinRT class directly or indirectly derives from the winrt::implements template. Daher kann das C++/WinRT-Objekt seine geschützte Memberfunktion implements::get_strong aufrufen, um einen starken Verweis auf ihren this-Zeiger abzurufen.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. Beachten Sie, dass es nicht erforderlich ist, die strong_this-Variable im Codebeispiel oben tatsächlich zu verwenden; einfach durch das Aufrufen von get_strong wird der Verweiszähler des C++/WinRT-Objekts erhöht und die Gültigkeit dessen impliziten this-Zeigers bewahrt.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.

Wichtig

Da get_strong eine Memberfunktion der Strukturvorlage winrt::implements ist, können Sie sie nur aus einer Klasse aufrufen, die direkt oder indirekt von winrt::implements abgeleitet ist, wie etwa eine C++/WinRT-Klasse.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. Weitere Informationen für das Ableiten aus winrt::implements und Beispiele finden Sie unter Erstellen von APIs mit C++/WinRT.For more info about deriving from winrt::implements, and examples, see Author APIs with C++/WinRT.

Dies behebt das Problem, das wir zuvor beim Erreichen von Schritt 4 hatten.This resolves the problem that we previously had when we got to step 4. Selbst wenn alle anderen Verweise auf die Klasseninstanz verschwinden, hat die Coroutine Vorsichtsmaßnahmen ergriffen, um sicherzustellen, dass ihre Abhängigkeiten stabil sind.Even if all other references to the class instance disappear, the coroutine has taken the precaution of guaranteeing that its dependencies are stable.

Wenn ein starker Verweis nicht geeignet ist, können Sie stattdessen implements::get_weak aufrufen, um einen schwachen Verweis auf this abzurufen.If a strong reference isn't appropriate, then you can instead call implements::get_weak to retrieve a weak reference to this. Bestätigen Sie einfach, dass Sie einen starken Verweis abrufen können, bevor Sie auf this zugreifen.Just confirm that you can retrieve a strong reference before accessing this. Auch get_weak stellt eine Memberfunktion der Strukturvorlage winrt::implements dar.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"";
    }
}

Im Beispiel oben verhindert der schwache Verweis die Zerstörung der Klasseninstanz nicht, wenn keine starken Verweise verbleiben.In the example above, the weak reference doesn't keep the class instance from being destroyed when no strong references remain. Sie gibt Ihnen aber die Möglichkeit, zu prüfen, ob ein starker Verweis erworben werden kann, bevor Sie auf die Membervariable zugreifen.But it gives you a way of checking whether a strong reference can be acquired before accessing the member variable.

Sicheres Zugreifen auf den this-Zeiger mit einem Delegaten für die EreignisbehandlungSafely accessing the this pointer with an event-handling delegate

SzenarioThe scenario

Allgemeine Informationen zum Ereignishandling finden Sie unter Verarbeiten von Ereignissen über Delegaten in C++/WinRT.For general info about event-handling, see Handle events by using delegates in C++/WinRT.

Im vorherigen Abschnitt wurden potenzielle Probleme mit der Lebensdauer in den Bereichen von Coroutinen und Parallelität behandelt.The previous section highlighted potential lifetime issues in the areas of coroutines and concurrency. Wenn Sie aber ein Ereignis innerhalb der Memberfunktion eines Objekts oder innerhalb einer Lambda-Funktion innerhalb der Mitgliedsfunktion eines Objekts verarbeiten, dann müssen Sie über die relative Lebensdauer des Ereignisempfängers (das Objekt, das das Ereignis verarbeitet) und der Ereignisquelle (das Objekt, das das Ereignis auslöst) nachdenken.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). Sehen wir uns einige Codebeispiele an.Let's look at some code examples.

Die Codeauflistung unten definiert zunächst eine einfache EventSource-Klasse, die ein allgemeines Ereignis auslöst, das von allen Stellvertretungen verarbeitet wird, die ihr hinzugefügt wurden.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. In diesem Beispielereignis wird der Stellvertretungstyp Windows::Foundation::EventHandler verwendet, die Probleme und Lösungen hier gelten aber für alle Stellvertretungstypen.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.

Anschließend stellt die EventRecipient-Klasse einen Handler für das EventSource::Event-Ereignis in Form einer Lambdafunktion bereit.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();
}

Das Muster besteht darin, dass der Ereignisempfänger einen Lambda-Ereignishandler mit Abhängigkeiten von seinem this-Zeiger aufweist.The pattern is that the event recipient has a lambda event handler with dependencies on its this pointer. Immer, wenn der Ereignisempfänger länger besteht als die Ereignisquelle, besteht er länger als diese Abhängigkeiten.Whenever the event recipient outlives the event source, it outlives those dependencies. Und in diesen Fällen, die häufig sind, funktioniert das Muster gut.And in those cases, which are common, the pattern works well. Einige dieser Fälle sind offensichtlich, z.B. wenn eine UI-Seite ein Ereignis verarbeitet, das von einem Steuerelement ausgelöst wird, das sich auf der Seite befindet.Some of these cases are obvious, such as when a UI page handles an event raised by a control that's on the page. Die Seite besteht länger als die Schaltfläche — also besteht der Handler auch länger als die Schaltfläche.The page outlives the button—so, the handler also outlives the button. Dies gilt immer dann, wenn der Empfänger die Quelle besitzt (z.B. als Datenelement), oder wenn der Empfänger und die Quelle gleichgeordnet sind und sich direkt im Besitz eines anderen Objekts befinden.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.

Wenn Sie wissen, dass der Handler nicht länger als der this-Zeiger besteht, von dem er abhängt, können Sie this auf normale Weise erfassen, ohne eine starke oder schwache Lebensdauer zu berücksichtigen.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.

Aber es gibt trotzdem Fälle, in denen this seine Verwendung in einem Handler nicht überlebt (einschließlich Handlern für Completion- und Progress-Ereignisse, die durch asynchrone Aktionen und Vorgänge ausgelöst werden), und es ist wichtig, zu wissen, wie mit ihnen umzugehen ist.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.

  • Wenn eine Ereignisquelle die Ereignisse synchron auslöst, kannst du den Handler widerrufen und sicher sein, dass keine weiteren Ereignisse empfangen werden.When an event source raises its events synchronously, you can revoke your handler and be confident that you won't receive any more events. Bei asynchronen Ereignissen kann jedoch auch nach dem Widerrufen (insbesondere bei einem Widerruf innerhalb des Destruktors) ein In-Flight-Ereignis das Objekt erreichen, nachdem mit der Zerstörung begonnen wurde.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. Das Problem lässt sich möglicherweise minimieren, indem die Abonnierung vor der Zerstörung aufgehoben wird. Im Folgenden stellen wir jedoch eine stabilere Lösung vor.Finding a place to unsubscribe prior to destruction might mitigate the issue, but continue reading for a robust solution.
  • Wenn Sie eine Coroutine erstellen, um eine asynchrone Methode zu implementieren, dann ist dies möglich.If you're authoring a coroutine to implement an asynchronous method, then it's possible.
  • In seltenen Fällen mit bestimmten XAML-UI-Framework-Objekten (z.B. SwapChainPanel) ist dies möglich, wenn der Empfänger finalisiert wird, ohne die Registrierung für die Ereignisquelle aufzuheben.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.

Das ProblemThe issue

Diese nächste Version der main-Funktion simuliert, was geschieht, wenn der Ereignisempfänger zerstört wird (möglicherweise überschreitet er seine Lebensdauer), während die Ereignisquelle weiterhin Ereignisse auslöst.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.
}

Der Ereignisempfänger wird zerstört, aber der Lambda-Ereignishandler in seinem Innern hat immer noch das EventEreignis abonniert.The event recipient is destroyed, but the lambda event handler within it is still subscribed to the Event event. Wenn das Ereignis ausgelöst wird, versucht die Lambda, den this-Zeiger zu dereferenzieren, der an diesem Punkt ungültig ist.When that event is raised, the lambda attempts to dereference the this pointer, which is at that point invalid. In diesen Fällen kommt es zu einer Zugriffsverletzung durch Code in einem Handler (oder in der Fortsetzung einer Coroutine), der versucht, ihn zu verwenden.So, an access violation results from code in the handler (or in a coroutine's continuation) attempting to use it.

Wichtig

Wenn Sie auf eine derartige Situation stoßen, dann müssen Sie über die Lebensdauer des this-Objekts nachdenken. Sie müssen feststellen, ob das verwendete this-Objekt überlebt oder nicht.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. Wenn nicht, dann verwenden Sie es mit einem starken oder schwachen Verweis, wie wir unten verdeutlichen werden.If it doesn't, then capture it with a strong or a weak reference, as we'll demonstrate below.

Wenn es in Ihrem Szenario sinnvoll ist und wenn Threading-Überlegungen dies zulassen, dann besteht eine andere Möglichkeit darin, den Handler zu widerrufen, nachdem der Empfänger mit dem Ereignis fertig ist, bzw im Destruktor des Empfängers.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. Mehr dazu erfahren Sie unter Einen registrierten Delegaten widerrufen.See Revoke a registered delegate.

Dies ist die Art, in der wir den Handler registrieren.This is how we're registering the handler.

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

Die Lambda erfasst automatisch alle lokalen Variablen durch Verweis.The lambda automatically captures any local variables by reference. Im Rahmen dieses Beispiels hätten wir also äquivalent dies schreiben können.So, for this example, we could equivalently have written this.

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

In beiden Fällen erfassen wir lediglich den nackten this-Zeiger.In both cases, we're just capturing the raw this pointer. Und das geschieht ohne Auswirkungen auf die Verweiszählung, es verhindert also nichts die Zerstörung des aktuellen Objekts.And that has no effect on reference-counting, so nothing is preventing the current object from being destroyed.

Die LösungThe solution

Die Lösung besteht darin, einen starken Verweis zu erfassen (oder, wie wir sehen werden, einen schwachen Verweis, wenn ein solcher besser geeignet ist.).The solution is to capture a strong reference (or, as we'll see, a weak reference if that's more appropriate). Ein starker Verweis setzt den Verweiszähler herauf, und er hält das aktuelle Objekt am Leben.A strong reference does increment the reference count, and it does keep the current object alive. Sie deklarieren einfach eine Erfassungsvariable (die in diesem Beispiel strong_this heißt) und initialisieren Sie mit einem Aufruf an implements::get_strong, die einen starken Verweis auf unseren this-Zeiger abruft.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.

Wichtig

Da get_strong eine Memberfunktion der Strukturvorlage winrt::implements ist, können Sie sie nur aus einer Klasse aufrufen, die direkt oder indirekt von winrt::implements abgeleitet ist, wie etwa eine C++/WinRT-Klasse.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. Weitere Informationen für das Ableiten aus winrt::implements und Beispiele finden Sie unter Erstellen von APIs mit 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;
});

Sie können sogar die automatische Erfassung des aktuellen Objekts fortlassen und über die Erfassungsvariable auf das Datenmember zugreifen, statt über den impliziten this-Verweis.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;
});

Wenn ein starker Verweis nicht geeignet ist, können Sie stattdessen implements::get_weak aufrufen, um einen schwachen Verweis auf this abzurufen.If a strong reference isn't appropriate, then you can instead call implements::get_weak to retrieve a weak reference to this. Ein schwacher Verweis behält das aktuelle Objekt nicht bei.A weak reference does not keep the current object alive. Bestätige einfach, dass vor dem Zugriff auf Member weiterhin ein starker Verweis aus dem schwachen Verweis abgerufen werden kann.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;
    }
});

Wenn du einen Rohzeiger erfasst, musst du sicherstellen, dass das Objekt, auf das gezeigt wird, beibehalten wird.If you capture a raw pointer, then you'll need to make sure you keep the pointed-to object alive.

Wenn Sie eine Memberfunktion als Stellvertretung verwendenIf you use a member function as a delegate

Ebenso wie für Lambda-Funktionen gelten diese Prinzipien auch für die Verwendung einer Memberfunktion als Stellvertretung.As well as lambda functions, these principles also apply to using a member function as your delegate. Die Syntax unterscheidet sich, also werfen wir einen Blick auf etwas Code.The syntax is different, so let's look at some code. Zunächst ist hier der potenziell unsichere Memberfunktions-Ereignishandler, der einen nackten this-Zeiger verwendet.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;
    }
};

Dies ist das herkömmliche Standardverfahren, um auf ein Objekt und seine Memberfunktion zu verweisen.This is the standard, conventional way to refer to an object and its member function. Um dies sicher zu gestalten, können Sie — beginnend mit Version 10.0.17763.0 (Windows 10, Version 1809) des Windows SDK — einen starken oder schwachen Verweis an dem Punkt einrichten, an dem der Handler registriert wird.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. An diesem Punkt ist bekannt, dass das Ereignisempfängerobjekt noch besteht.At that point, the event recipient object is known to be still alive.

Rufen Sie für einen starken Verweis einfach get_strong anstelle des nackten this-Zeigers auf.For a strong reference, just call get_strong in place of the raw this pointer. C++/ WinRT stellt sicher, dass die resultierende Stellvertretung einen starken Verweis auf das aktuelle Objekt enthält.C++/WinRT ensures that the resulting delegate holds a strong reference to the current object.

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

Das Erfassen eines starken Verweises bedeutet, dass das Objekt erst zur Zerstörung verfügbar wird, nachdem die Registrierung des Handlers aufgehoben wurde und alle ausstehenden Rückrufe zurückgegeben wurden.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. Diese Garantie gilt aber nur zu dem Zeitpunkt, zu dem das Ereignis ausgelöst wird.However, that guarantee is valid only at the time the event is raised. Wenn der Ereignishandler asynchron ist, muss die Coroutine einen starken Verweis auf die Klasseninstanz vor dem ersten Anhaltepunkt erhalten (Informationen und Code dazu findest du im Abschnitt Sicherer Zugriff auf denthis-Zeiger in einer Klassenmember-Coroutine weiter oben in diesem Thema).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). Dadurch entsteht jedoch ein Zirkelbezug zwischen der Ereignisquelle und deinem Objekt, daher muss dieser durch Widerrufen des Ereignisses explizit unterbrochen werden.But that creates a circular reference between the event source and your object, so you need to explicitly break that by revoking your event.

Für einen schwachen Verweis rufen Sie get_weak auf.For a weak reference, call get_weak. C++/ WinRT stellt sicher, dass die resultierende Stellvertretung einen schwachen Verweis enthält.C++/WinRT ensures that the resulting delegate holds a weak reference. Hinter den Kulissen versucht die Stellvertretung in letzter Minute, den schwachen Verweis in einen starken aufzulösen und ruft die Memberfunktion nur auf, wenn dies Erfolg hat.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 });

Wenn der Delegat die Memberfunktion tatsächlich aufruft, behält C++/WinRT das Objekt bei, bis der Handler zurückgegeben wird.If the delegate does call your member function, then C++/WinRT will keep your object alive until your handler returns. Wenn der Handler jedoch asynchron ist, wird er an Anhaltepunkten zurückgegeben, daher muss die Coroutine vor dem ersten Anhaltepunkt einen starken Verweis auf die Klasseninstanz erhalten.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. Weitere Informationen findest du ebenfalls im Abschnitt Sicherer Zugriff auf den this-Zeiger in einer Klassenmember-Coroutine weiter oben in diesem Thema.Again, for more info, see Safely accessing the this pointer in a class-member coroutine section earlier in this topic.

Beispiel für einen schwachen Verweis mithilfe von SwapChainPanel::CompositionScaleChangedA weak reference example using SwapChainPanel::CompositionScaleChanged

In diesem Codebeispiel verwenden wir das Ereignis SwapChainPanel::CompositionScaleChanged als eine weitere Demonstration für schwache Verweise.In this code example, we use the SwapChainPanel::CompositionScaleChanged event by way of another illustration of weak references. Der Code registriert einen Ereignishandler unter Verwendung einer Lambda-Funktion, die einen schwachen Verweis auf den Empfänger erfasst.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.
}

In der Lambda-Erfassungsklausel wird eine temporäre Variable erstellt, die einen schwachen Verweis auf this darstellt.In the lamba capture clause, a temporary variable is created, representing a weak reference to this. In der Lambda wird die Funktion OnCompositionScaleChanged aufgerufen, wenn ein starker Verweis auf this abgerufen werden kann.In the body of the lambda, if a strong reference to this can be obtained, then the OnCompositionScaleChanged function is called. Auf diese Weise kann this innerhalb von OnCompositionScaleChanged sicher verwendet werden.That way, inside OnCompositionScaleChanged, this can safely be used.

Schwache Verweise in C++/WinRTWeak references in C++/WinRT

Oben haben wir die Verwendung schwacher Verweise gesehen.Above, we saw weak references being used. Im Allgemeinen sind sie zum Aufbrechen von Zirkelverweisen zu gebrauchen.In general, they're good for breaking cyclic references. Beispielsweise ist der schwache Verweismechanismus bei der nativen Implementierung des XAML-basierten UI-Frameworks —aufgrund des historischen Entwurfs des Frameworks— in C++/WinRT notwendig, um Zirkelverweise zu verarbeiten.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. Außerhalb von XAML werden Sie schwache Verweise aber vermutlich nicht verwenden müssen (nicht, dass sie etwas XAML-Spezifisches an sich hätten).Outside of XAML, though, you likely won't need to use weak references (not that there's anything inherently XAML-specific about them). Sie sollten vielmehr meistens in der Lage sein, Ihre eigenen C++/WinRT-APIs so zu gestalten, dass Zirkelverweise und schwache Verweise vermieden werden.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.

Bei einem von Ihnen deklarierten Typ ist es für C++/WinRT nicht sofort ersichtlich, ob oder wann schwache Verweise benötigt werden.For any given type that you declare, it's not immediately obvious to C++/WinRT whether or when weak references are needed. Daher bietet C++/WinRT für die Strukturvorlage winrt::implements automatisch eine Unterstützung von schwachen Verweisen. Von dieser werden Ihre eigenen C++/WinRT-Typen direkt oder indirekt abgeleitet.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. Dies kostet Sie nichts, es sei denn, Ihr Objekt wird tatsächlich auf IWeakReferenceSource abgefragt.It's pay-for-play, in that it doesn't cost you anything unless your object is actually queried for IWeakReferenceSource. Und Sie können sich explizit gegen diese Unterstützung entscheiden.And you can choose explicitly to opt out of that support.

CodebeispieleCode examples

Die Strukturvorlage winrt::weak_ref ist eine Option, um einen schwachen Verweis auf eine Klasseninstanz zu erhalten.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 };

Oder Sie können die Hilfsfunktion winrt::make_weak verwenden.Or, you can use the use the winrt::make_weak helper function.

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

Die Erstellung eines schwachen Verweises hat keinen Einfluss auf die Anzahl der Verweise auf das Objekt selbst, sondern bewirkt lediglich die Zuweisung eines Kontrollblocks.Creating a weak reference doesn't affect the reference count on the object itself; it just causes a control block to be allocated. Dieser Kontrollblock kümmert sich um die Implementierung der schwachen Verweissemantik.That control block takes care of implementing the weak reference semantics. Sie können dann versuchen, den schwachen Verweis auf einen starken Verweis hochzustufen (und, wenn dies erfolgreich ist, ihn zu verwenden).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();
}

Sofern noch ein anderer starker Verweis existiert, erhöht der Aufruf von weak_ref::get den Verweiszähler und gibt den starken Verweis an den Aufrufer zurück.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.

Verzicht auf die Unterstützung von schwachen VerweisenOpting out of weak reference support

Die Unterstützung schwacher Verweise erfolgt automatisch.Weak reference support is automatic. Sie können diese Unterstützung jedoch explizit deaktivieren, indem Sie die winrt::no_weak_ref-Markerstruktur als Vorlagenargument an Ihre Basisklasse übergeben.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.

Wenn Sie direkt von winrt::implements ableiten.If you derive directly from winrt::implements.

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

Wenn Sie eine Laufzeitklasse erstellen.If you're authoring a runtime class.

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

Dabei spielt es keine Rolle, wo im variadic-Parameterpaket die Markerstruktur verwendet wird.It doesn't matter where in the variadic parameter pack the marker struct appears. Wenn Sie einen schwachen Verweis für einen Opted-Out-Typ anfordern, dann hilft Ihnen der Compiler mit der Meldung „Dies ist nur für die Unterstützung schwacher Verweise“.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".

Wichtige APIsImportant APIs