Сильные и слабые ссылки в C++/WinRTStrong and weak references in C++/WinRT

Среда выполнения Windows — это система с учетом ссылок. В подобной системе очень важно знать о значении сильных и слабых ссылок, а также о различиях между ними (и о других ссылках, например о неявном указателе this).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). Сведения в этой статье помогут вам понять, что при правильном управлении этими ссылками вы получите надежную, эффективно функционирующую систему, а не систему, в которой происходят непредсказуемые сбои.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. Предоставление вспомогательных функций с мощной поддержкой в проекции языка C++/WinRT удовлетворит ваши требования к простой и правильной работе по построению более сложных систем.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.

Безопасный доступ к указателю this в сопрограмме членов классаSafely accessing the this pointer in a class-member coroutine

Дополнительные сведения о соподпрограммах и примеры кода приведены в разделе Параллельная обработка и асинхронные операции с помощью C++/WinRT.For more info about coroutines, and code examples, see Concurrency and asynchronous operations with C++/WinRT.

Ниже приведен типичный пример кода сопрограммы, которая является функцией-членом класса.The code listing below shows a typical example of a coroutine that's a member function of a class. Вы можете скопировать и вставить этот пример в указанные файлы нового проекта консольного приложения для 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 проработает некоторое время и в конечном итоге вернет копию элемента данных MyClass::m_value.MyClass::RetrieveValueAsync spends some time working, and eventually it returns a copy of the MyClass::m_value data member. Вызов RetrieveValueAsync приводит к созданию асинхронного объекта, который содержит неявный указатель this (через который в конечном итоге осуществляется доступ к 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).

Учитывайте, что в соподпрограмме выполнение происходит синхронно до первой точки приостановки, где управление возвращается вызывающему объекту.Remember that, in a coroutine, execution is synchronous up until the first suspension point, where control is returned to the caller. В RetrieveValueAsync первое ключевое слово co_await — это первая точка приостановки.In RetrieveValueAsync, the first co_await is the first suspension point. К тому времени, когда соподпрограмма возобновит работу (в нашем случае примерно через пять секунд), что угодно может повлиять на неявный указатель this, обеспечивающий доступ к 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.

Ниже приведена полная последовательность событий.Here's the full sequence of events.

  1. В main создается экземпляр MyClass (myclass_instance).In main, an instance of MyClass is created (myclass_instance).
  2. Создается объект async, указывающий на myclass_instance (через this).The async object is created, pointing (via its this) to myclass_instance.
  3. Функция Winrt::Windows::Foundation::IAsyncAction::get достигнет первой точки приостановки, заблокируется на несколько секунд, а затем вернет результат 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 возвращает значение this->m_value.RetrieveValueAsync returns the value of this->m_value.

Шаг 4 безопасен, только если указатель this остается допустимым.Step 4 is safe only as long as this remains valid.

Но что, если экземпляр класса уничтожается до завершения асинхронной операции?But what if the class instance is destroyed before the async operation completes? Существует множество возможностей, с помощью которых экземпляр класса может выходить за пределы области до завершения асинхронного метода.There are all kinds of ways the class instance could go out of scope before the asynchronous method has completed. Но можно имитировать это, установив для экземпляра класса значение 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;
}

После уничтожения экземпляра класса все выглядит так, будто мы не ссылаемся непосредственно на него снова.After the point where we destroy the class instance, it looks like we don't directly refer to it again. Но конечно асинхронный объект содержит указатель this на него и пытается использовать его, чтобы скопировать значение, хранящееся в экземпляре класса.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. Сопрограмма является функцией-членом и она ожидает возможность, чтобы свободно использовать свой указатель this.The coroutine is a member function, and it expects to be able to use its this pointer with impunity.

С таким изменением кода мы сталкиваемся с проблемой на шаге 4, так как экземпляр класса уничтожен и this больше не является допустимым.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. Как только асинхронный объект сразу попытается получить доступ к переменной внутри экземпляра класса, произойдет сбой (или случится что-то неопределенное).As soon as the asynchronous object attempts to access the variable inside the class instance, it will crash (or do something entirely undefined).

Решение заключается в предоставлении асинхронной операции —сопрограмме — собственной сильной ссылки на экземпляр класса.The solution is to give the asynchronous operation—the coroutine—its own strong reference to the class instance. В настоящее время запись сопрограммы фактически содержит необработанный указатель this на экземпляр класса, но этого недостаточно для сохранения экземпляра класса.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.

Для сохранения экземпляра класса измените реализацию RetrieveValueAsync как показано ниже.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;
}

Класс C++/WinRT прямо или косвенно является производным от шаблона winrt::implements.A C++/WinRT class directly or indirectly derives from the winrt::implements template. Поэтому объект C++/WinRT может вызывать свою защищенную функцию-член Implements::get_strong, чтобы получить точную ссылку для своего указателя 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. Обратите внимание, что нет необходимости использовать переменную strong_this в приведенном выше примере кода; просто вызов get_strong увеличивает количество ссылок объекта C++/WinRT и указатель this остается допустимым.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.

Важно!

Так как get_strong — это функция-член шаблона структуры winrt::implements, вы можете вызвать ее только из класса, который прямо или косвенно является производным от winrt::implements, например класс 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. Дополнительные сведения о производных от winrt::implements и примерах см. в статье Author APIs with C++/WinRT (Создание API-интерфейсов с помощью C++/WinRT).For more info about deriving from winrt::implements, and examples, see Author APIs with C++/WinRT.

Это решает предыдущую проблему на шаге 4.This resolves the problem that we previously had when we got to step 4. Даже если остальные ссылки на экземпляр класса исчезнут, сопрограмма предпримет меры предосторожности, которые гарантируют, что ее зависимости стабильны.Even if all other references to the class instance disappear, the coroutine has taken the precaution of guaranteeing that its dependencies are stable.

Если сильная ссылка не подходит, то вместо этого вы можете вызвать implements::get_weak, чтобы получить слабую ссылку для this.If a strong reference isn't appropriate, then you can instead call implements::get_weak to retrieve a weak reference to this. Просто убедитесь, что можно получить сильную ссылку перед доступом к this.Just confirm that you can retrieve a strong reference before accessing this. Опять же get_weak является функцией-членом шаблона структуры 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"";
    }
}

В приведенном выше примере слабая ссылка не предохраняет экземпляр класса от уничтожения, если не осталось сильных ссылок.In the example above, the weak reference doesn't keep the class instance from being destroyed when no strong references remain. Но вы сможете проверить, можно ли получить сильную ссылку до обращения к переменной-члену.But it gives you a way of checking whether a strong reference can be acquired before accessing the member variable.

Безопасный доступ к указателю this с помощью делегата, обрабатывающего событияSafely accessing the this pointer with an event-handling delegate

СценарийThe scenario

Общие сведения об обработке событий см. в статье Handle events by using delegates in C++/WinRT (Обработка событий с помощью делегатов в C++/WinRT).For general info about event-handling, see Handle events by using delegates in C++/WinRT.

В предыдущем разделе выделены потенциальные проблемы времени существования в областях сопрограмм и параллельной обработки.The previous section highlighted potential lifetime issues in the areas of coroutines and concurrency. Но если нужно обработать событие с функцией-членом объекта или из лямбда-функции внутри функции-члена объекта, необходимо подумать об относительном времени существования получателя события (объекта, обрабатывающего событие) и источника события (объекта, вызывающего событие).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). Рассмотрим несколько примеров кода.Let's look at some code examples.

Код ниже сначала определяет простой класс EventSource, вызывающий общее событие, которое обрабатывается добавленными к нему делегатами.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. В этом примере событие использует тип делегата Windows::Foundation::EventHandler, но проблемы и способы их устранения, рассмотренные здесь, применимы ко всем типам делегатов.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.

Затем класс EventRecipient предоставляет обработчик для события EventSource::Event в виде лямбда-функции.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();
}

Шаблоном является то, что получатель события содержит обработчик событий лямбда-выражений с зависимостями своего указателя this.The pattern is that the event recipient has a lambda event handler with dependencies on its this pointer. Каждый раз, когда время существования получателя события превышает время существования источника события, оно превышает время существования зависимостей.Whenever the event recipient outlives the event source, it outlives those dependencies. И в таких распространенных случаях шаблон работает хорошо.And in those cases, which are common, the pattern works well. Некоторые из таких случаев очевидны, например при обработке страницей пользовательского интерфейса события, вызванного элементом управления, который находится на этой странице.Some of these cases are obvious, such as when a UI page handles an event raised by a control that's on the page. Время существования страницы превышает время существования кнопки.—Таким образом, обработчик также превышает время существования кнопки.The page outlives the button—so, the handler also outlives the button. Эта ситуация верна всегда, когда получатель является владельцем источника (например, в качестве элемента данных), или когда получатель и источник являются родственными и напрямую принадлежат другому объекту.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.

Если вы уверены, что в вашей ситуации время существования обработчика не превышает время существования объекта this, от которого он зависит, можно записать this обычным образом, без учета времени существования.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.

Однако бывают случаи, когда время существования объекта this не превышает время его использования в обработчике (включая обработчики событий завершения и выполнения, которые создаются асинхронными действиями и операциями), и важно знать как с ними обращаться.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.

  • Когда события поступают из источника синхронно, вы можете отозвать свой дескриптор. При этом события больше поступать не будут.When an event source raises its events synchronously, you can revoke your handler and be confident that you won't receive any more events. Но для асинхронных событий даже после отмены (и особенно после отмены в деструкторе) обрабатываемое событие может достичь объекта после начала уничтожения.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. Попытка отменить подписку перед уничтожением может решить проблему, но вам нужно найти надежное решение.Finding a place to unsubscribe prior to destruction might mitigate the issue, but continue reading for a robust solution.
  • Это возможно, если вы создаете сопрограмму для реализации асинхронного метода.If you're authoring a coroutine to implement an asynchronous method, then it's possible.
  • В редких случаях с определенными объектами платформы пользовательского интерфейса XAML (например, SwapChainPanel) это возможно, если получатель завершается без отмены регистрации в источнике событий.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.

ПроблемаThe issue

Следующая версия этой функции main имитирует, что случится при уничтожении получателя события (возможно с выходом за пределы области), пока источник событий по-прежнему их вызывает.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.
}

Получатель события уничтожается, но обработчик событий лямбда-выражений в нем по-прежнему является подписанным на событие Event.The event recipient is destroyed, but the lambda event handler within it is still subscribed to the Event event. При возникновении этого события лямбда-выражение пытается разыменовать указатель this, который недопустим в этот момент.When that event is raised, the lambda attempts to dereference the this pointer, which is at that point invalid. Таким образом, нарушение прав доступа происходит из-за кода в обработчике (или в продолжении сопрограммы), который пытается его использовать.So, an access violation results from code in the handler (or in a coroutine's continuation) attempting to use it.

Важно!

Если вы столкнулись с подобной ситуацией, вам следует подумать о времени существования объекта this и о том, превышает ли время существования записанного объекта this время существования захвата.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. Если нет, записывайте его с помощью сильной или слабой ссылки, как будет показано ниже.If it doesn't, then capture it with a strong or a weak reference, as we'll demonstrate below.

Или —если это целесообразно для вашего сценария и потоковая модель это позволяет — вы можете отозвать обработчик после того, как получатель завершил работу с событием, либо в деструкторе получателя.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. Ознакомьтесь с разделом Отзыв зарегистрированного делегата.See Revoke a registered delegate.

Вот как можно зарегистрировать обработчик.This is how we're registering the handler.

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

Лямбда-выражение автоматически захватывает все локальные переменные по ссылке.The lambda automatically captures any local variables by reference. Таким образом, в этом примере мы могли бы записать это так.So, for this example, we could equivalently have written this.

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

В обоих случаях мы просто захватываем необработанный указатель this.In both cases, we're just capturing the raw this pointer. И это не оказывает влияния на учет ссылок, поэтому ничто не мешает уничтожить текущий объект.And that has no effect on reference-counting, so nothing is preventing the current object from being destroyed.

РешениеThe solution

Решение заключается в записи строгой ссылки (или, как мы увидим, слабой ссылки, если это более уместно).The solution is to capture a strong reference (or, as we'll see, a weak reference if that's more appropriate). Сильная ссылка увеличивает количество ссылок и сохраняет текущий объект.A strong reference does increment the reference count, and it does keep the current object alive. Вы просто объявляете переменную захвата (в этом примере вызывается strong_this) и инициализируете ее с помощью вызова функции-члена implements::get_strong, которая получает точную ссылку на наш указатель 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.

Важно!

Так как get_strong — это функция-член шаблона структуры winrt::implements, вы можете вызвать ее только из класса, который прямо или косвенно является производным от winrt::implements, например класс 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. Дополнительные сведения о производных от winrt::implements и примерах см. в статье Author APIs with C++/WinRT (Создание API-интерфейсов с помощью 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;
});

Вы можете даже опустить автоматическую запись текущего объекта и доступ к элементу данных через запись переменной вместо неявного 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;
});

Если сильная ссылка не подходит, то вместо этого вы можете вызвать implements::get_weak, чтобы получить слабую ссылку для this.If a strong reference isn't appropriate, then you can instead call implements::get_weak to retrieve a weak reference to this. Слабая ссылка не сохраняет текущий объект.A weak reference does not keep the current object alive. Просто убедитесь, что вы можете получить сильную ссылку из слабой ссылки перед получением доступа к элементам.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;
    }
});

При записи необработанного указателя необходимо убедиться в наличии объекта, на который эта ссылка указывает.If you capture a raw pointer, then you'll need to make sure you keep the pointed-to object alive.

Использование функции-члена в качестве делегатаIf you use a member function as a delegate

Как и лямбда-функции, эти принципы также применимы к использованию функции-члена в качестве вашего делегата.As well as lambda functions, these principles also apply to using a member function as your delegate. Синтаксис отличается, поэтому рассмотрим код.The syntax is different, so let's look at some code. Во-первых, вот потенциально небезопасный обработчик событий функции-члена, использующий необработанный указатель this.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;
    }
};

Это стандартный, обычный способ ссылки на объект и его функцию-члена.This is the standard, conventional way to refer to an object and its member function. Чтобы сделать это безопасно, вы можете — начиная с версии 10.0.17763.0 Windows SDK (Windows 10, версия 1809) — установить сильную или слабую ссылку там, где зарегистрирован обработчик.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. При этом объект получателя событий по-прежнему активен.At that point, the event recipient object is known to be still alive.

Для сильной ссылки просто вызовите get_strong в место необработанного указателя this.For a strong reference, just call get_strong in place of the raw this pointer. C++/WinRT гарантирует, что результирующий делегат содержит сильную ссылку на текущий объект.C++/WinRT ensures that the resulting delegate holds a strong reference to the current object.

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

Запись строгой ссылки означает, что объект можно будет уничтожить только после отмены регистрации дескриптора и получения всех невыполненных обратных вызовов.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. Но это справедливо только на момент возникновения события.However, that guarantee is valid only at the time the event is raised. Если дескриптор событий является асинхронным, вам нужно предоставить сопрограмме строгую ссылку на экземпляр класса перед первой точкой приостановки (дополнительные сведения и код см. в инструкциях по безопасному доступу к указателю this в сопрограмме членов класса выше).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). Но это создает циклическую ссылку между источником события и объектом, которую нужно явно разорвать, отозвав событие.But that creates a circular reference between the event source and your object, so you need to explicitly break that by revoking your event.

Для слабой ссылки вызовите get_weak.For a weak reference, call get_weak. C++/WinRT гарантирует, что результирующий делегат содержит слабую ссылку.C++/WinRT ensures that the resulting delegate holds a weak reference. Незаметно в последнюю минуту делегат пытается заменить слабую ссылку на сильную и вызвать функцию-член только при успешном выполнении.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 });

Если делегат вызывает функцию-член, C++/WinRT сохранит объект до возврата дескриптора.If the delegate does call your member function, then C++/WinRT will keep your object alive until your handler returns. Но если дескриптор является асинхронным, он возвращается в точках приостановки, поэтому вам нужно предоставить сопрограмме строгую ссылку на экземпляр класса перед первой точкой приостановки.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. См. подробнее о безопасном доступе к указателю this в сопрограмме членов класса выше.Again, for more info, see Safely accessing the this pointer in a class-member coroutine section earlier in this topic.

Пример слабых ссылок при использовании SwapChainPanel::CompositionScaleChangedA weak reference example using SwapChainPanel::CompositionScaleChanged

В этом примере кода в качестве другой иллюстрации слабой ссылки мы используем событие SwapChainPanel::CompositionScaleChanged.In this code example, we use the SwapChainPanel::CompositionScaleChanged event by way of another illustration of weak references. Код осуществляет регистрацию обработчика событий с помощью лямбда-функции, создающей слабую ссылку на получатель.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.
}

В предложении захвата лямбда-функции создается временная переменная, представляющая собой слабую ссылку на объект this.In the lamba capture clause, a temporary variable is created, representing a weak reference to this. В теле лямбда-функции, если можно получить строгую ссылку на объект this, вызывается функция OnCompositionScaleChanged.In the body of the lambda, if a strong reference to this can be obtained, then the OnCompositionScaleChanged function is called. Таким образом внутри OnCompositionScaleChanged можно безопасно использовать объект this.That way, inside OnCompositionScaleChanged, this can safely be used.

Слабые ссылки в C++/WinRTWeak references in C++/WinRT

Выше мы видели использование слабых ссылок.Above, we saw weak references being used. Как правило они хорошо подходят для разрыва циклических ссылок.In general, they're good for breaking cyclic references. Например, когда речь идет о собственной реализации платформы пользовательского интерфейса на основе XAML (ввиду исторических особенностей проектирования платформы) механизм слабых ссылок в C++/WinRT требуется для обработки циклических ссылок.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. За пределами XAML вам, скорее всего, не потребуется использовать слабые ссылки (хотя в них нет ничего, изначально относящегося непосредственно к XAML).Outside of XAML, though, you likely won't need to use weak references (not that there's anything inherently XAML-specific about them). Проектирование API-интерфейсов C++/WinRT чаще всего можно произвести таким образом, чтобы избежать необходимости циклических и слабых ссылок.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.

Для конкретного объявляемого вами типа C++/WinRT на первый взгляд не очевидно, требуются ли слабые ссылки, и если да, то когда.For any given type that you declare, it's not immediately obvious to C++/WinRT whether or when weak references are needed. Поэтому C++/ WinRT автоматически обеспечивает поддержку слабых ссылок в шаблоне структуры winrt::implements, от которого напрямую или косвенно наследуются ваши собственные типы 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. Поддержка слабых ссылок не расходует ресурсов до тех пор, пока у вашего объекта не запрашивается IWeakReferenceSource.It's pay-for-play, in that it doesn't cost you anything unless your object is actually queried for IWeakReferenceSource. Кроме того, вы можете явным образом отказаться от этой поддержки.And you can choose explicitly to opt out of that support.

Примеры кодаCode examples

Шаблон структуры winrt::weak_ref является одним из вариантов получения слабой ссылки на экземпляр класса.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 };

Кроме того, можно использовать вспомогательную функцию winrt::make_weak.Or, you can use the use the winrt::make_weak helper function.

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

Создание слабой ссылки не влияет на количество ссылок на сам объект, а просто приводит к выделению блока управления.Creating a weak reference doesn't affect the reference count on the object itself; it just causes a control block to be allocated. Этот блок управления отвечает за реализацию семантики слабой ссылки.That control block takes care of implementing the weak reference semantics. Затем вы можете попытаться превратить слабую ссылку в сильную и в случае успеха ее использовать.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();
}

При условии, что существуют другие строгие ссылки, вызов weak_ref::get увеличивает количество ссылок и возвращает сильную ссылку в вызывающий код.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.

Отказ от поддержки слабых ссылокOpting out of weak reference support

Поддержка слабых ссылок осуществляется автоматически.Weak reference support is automatic. Однако вы можете явным образом отказаться от этой поддержки, передав структуру маркера winrt::no_weak_ref в качестве аргумента шаблона базовому классу.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.

При непосредственном наследовании из winrt::implements.If you derive directly from winrt::implements.

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

При создании класса среды выполнения:If you're authoring a runtime class.

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

Не имеет значения, где в пакете параметров располагается структура маркера.It doesn't matter where in the variadic parameter pack the marker struct appears. При запросе слабой ссылки для типа с отключенной поддержкой компилятор отобразит сообщение This is only for weak ref support (Только для поддержки слабых ссылок).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".

Важные APIImportant APIs