C++/WinRT의 강한 참조 및 약한 참조Strong 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. 코루틴이 다시 시작될 때까지(이 경우 약 5초 정도) m_value에 액세스하는 암시적 this 포인터가 변경되었을 수 있습니다.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 개체가 생성되고 this를 통해 myclass_instance를 가리킵니다.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. RetrieveValueAsyncthis->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. 코루틴은 멤버 함수이며, impunity와 함께 this 포인터를 사용할 수 있어야 합니다.The coroutine is a member function, and it expects to be able to use its this pointer with impunity.

코드를 이렇게 변경하면, 클래스 인스턴스가 삭제되고 this가 더 이상 유효하지 않으므로 4단계에서 문제가 발생합니다.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 protected 멤버 함수를 호출하여 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_strongwinrt::implements 구조체 템플릿의 멤버 함수이므로, C++/WinRT 클래스와 같이 winrt::implements에서 직간접적으로 파생된 클래스에서만 호출할 수 있습니다.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에서 파생하는 방법에 대한 자세한 내용과 예제는 C++/WinRT를 통한 API 작성을 참조하세요.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_weakwinrt::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

이벤트 처리에 대한 일반적인 내용은 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. UI 페이지가 해당 페이지에 있는 컨트롤에서 발생한 이벤트를 처리하는 경우와 같이 이러한 경우 중 일부는 쉽게 이해할 수 있습니다.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 UI 프레임워크 개체(예: 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에 대한 호출로 초기화합니다.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_strongwinrt::implements 구조체 템플릿의 멤버 함수이므로, C++/WinRT 클래스와 같이 winrt::implements에서 직간접적으로 파생된 클래스에서만 호출할 수 있습니다.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에서 파생하는 방법에 대한 자세한 내용과 예제는 C++/WinRT를 통한 API 작성을 참조하세요.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. 이 처리기를 안전하게 만들기 위해 Windows SDK 버전 10.0.17763.0(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.

강한 참조의 경우 원시 this 포인터 대신 get_strong을 호출하면 됩니다.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::CompositionScaleChanged를 사용하는 약한 참조 예제A 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++/WinRT의 약한 참조Weak references in C++/WinRT

위에서, 약한 참조를 사용하는 방법을 살펴보았습니다.Above, we saw weak references being used. 일반적으로 약한 참조는 순환 참조를 중단하는 데 적합합니다.In general, they're good for breaking cyclic references. 예를 들어 XAML 기반 UI 프레임워크의 기본 구현에서는 프레임워크의 이전 설계 때문에 순환 참조를 처리하기 위해 C++/WinRT의 약한 참조 메커니즘이 필요합니다.For example, for the native implementation of the XAML-based UI framework—because of the historic 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). 대신, 순환 참조와 약한 참조가 필요하지 않는 방식으로 고유한 C++/WinRT API를 설계할 수 있어야 합니다.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는 고유한 C++/WinRT 형식이 직간접적으로 파생되는 구조체 템플릿 winrt::implements에서 약한 참조 지원을 자동으로 제공합니다.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에 대해 개체를 실제로 쿼리하는 경우에만 비용이 발생한다는 점에서 P4P(Pay-for-Play)입니다.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>
{
    ...
}

variadic 매개 변수 팩에서 마커 구조체가 표시되는 위치는 중요하지 않습니다.It doesn't matter where in the variadic parameter pack the marker struct appears. 옵트아웃된 형식의 약한 참조를 요청하는 경우 컴파일러에서 “약한 참조 지원에만 사용됩니다.” 오류를 도와줍니다. 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