C++/WinRT
이러한 실수는 디버그하기 어렵고 시간이 많이 걸리는 신비한 충돌 또는 손상으로 바뀔 수 있습니다. 따라서 이것은 중요한 기능이며 배경을 이해하는 것이 좋습니다.
MyStringable을 사용하여 장면 설정
먼저 간단한 IStringable구현을 살펴보겠습니다.
struct MyStringable : implements<MyStringable, IStringable>
{
winrt::hstring ToString() const { return L"MyStringable"; }
};
이제 IStringable 인수로 예상하는 함수(구현 내에서)를 호출해야 한다고 상상해 보십시오.
void Print(IStringable const& stringable)
{
printf("%ls\n", stringable.ToString().c_str());
}
문제는 MyStringable 형식이 IStringable아니라는 것입니다.
- MyStringable 형식은 IStringable 인터페이스의 구현입니다.
- IStringable 형식은 프로젝션된 형식입니다.
중요합니다
구현 형식과 예상 형식의 차이를 이해하는 것이 중요합니다. 필수 개념 및 용어의 경우 C++/WinRT API 사용
구현과 프로젝션 사이의 공간은 미묘하게 파악할 수 있습니다. 구현이 프로젝션과 조금 더 유사하게 느껴지도록 하기 위해, 구현은 구현하는 각 프로젝션된 형식에 대한 암시적 변환을 제공합니다. 그렇다고 해서 단순히 이 작업을 수행할 수 있는 것은 아닙니다.
struct MyStringable : implements<MyStringable, IStringable>
{
winrt::hstring ToString() const;
void Call()
{
Print(this);
}
};
대신 변환 연산자를 호출 해결을 위한 후보로 사용할 수 있도록 참조를 가져와야 합니다.
void Call()
{
Print(*this);
}
그것은 작동합니다. 암시적 변환은 구현 형식에서 프로젝션된 형식으로의 (매우 효율적인) 변환을 제공하므로 많은 시나리오에서 매우 편리합니다. 해당 기능이 없으면 많은 구현 유형이 작성하기에 매우 번거로울 것입니다. winrt::make 함수 템플릿(또는 winrt::make_self)만 사용하여 구현을 할당하는 경우 모든 것이 좋습니다.
IStringable stringable{ winrt::make<MyStringable>() };
C++/WinRT 1.0의 잠재적인 문제
하지만 암시적 변환으로 인해 문제가 발생할 수 있습니다. 도움이 되지 않는 보조 함수를 고려해 보세요.
IStringable MakeStringable()
{
return MyStringable(); // Incorrect.
}
또는 심지어 이 분명히 무해한 표현.
IStringable stringable{ MyStringable() }; // Also incorrect.
아쉽게도 이러한 같은 코드는 암시적 변환으로 인해 C++/WinRT 1.0으로 컴파일하지. (매우 심각한) 문제는 우리가 기반 메모리가 단명 스택에 있는 참조 개수 개체를 가리키는 프로젝션된 타입을 반환할 수 있다는 점입니다.
C++/WinRT 1.0으로 컴파일된 다른 항목은 다음과 같습니다.
MyStringable* stringable{ new MyStringable() }; // Very inadvisable.
원시 포인터는 위험하고 노동 집약적인 버그 소스입니다. 필요하지 않은 경우 사용하지 마세요. C++/WinRT는 원시 포인터를 사용하지 않도록 하면서 모든 것을 효율적으로 만드는 데 특별한 노력을 기울입니다. C++/WinRT 1.0으로 컴파일된 다른 항목은 다음과 같습니다.
auto stringable{ std::make_shared<MyStringable>(); } // Also very inadvisable.
이것은 여러 수준에서 실수입니다. 동일한 개체에 대해 두 개의 서로 다른 참조 수가 있습니다. Windows 런타임(및 이전의 클래식 COM)은 std::shared_ptr호환되지 않는 기본 참조 수를 기반으로 합니다. std::shared_ptr는 물론 여러 가지 유효한 용례가 있습니다. 하지만 Windows 런타임(및 클래식 COM) 개체를 공유할 때는 필요가 없습니다. 마지막으로 C++/WinRT 1.0으로 컴파일됩니다.
auto stringable{ std::make_unique<MyStringable>() }; // Highly dubious.
이것은 다시 다소 의문스럽습니다. 고유 소유권은 MyStringable의 고유 참조 수의 공유 수명과 대조됩니다.
C++/WinRT 2.0을 사용한 솔루션
C++/WinRT 2.0에서는 이러한 모든 구현 형식을 직접 할당하려고 하면 컴파일러 오류가 발생합니다. 이것이 가장 좋은 오류이며 신비한 런타임 버그보다 무한히 낫습니다.
구현을 해야 할 때마다, 위에 설명된 대로 winrt::make 또는 winrt::make_self을 간단히 사용할 수 있습니다. 이제 이 작업을 잊어버린 경우 use_make_function_to_create_this_object추상 함수에 대한 참조와 함께 이를 암시하는 컴파일러 오류가 표시됩니다. 그것은 정확히 static_assert
은 아니지만 가깝습니다. 그러나 이것은 설명된 모든 실수를 감지하는 가장 신뢰할 수 있는 방법입니다.
구현에 몇 가지 사소한 제약 조건을 배치해야 한다는 의미입니다. 재정정이 없는 경우를 이용하여 직접 할당을 감지하고자 하기 때문에, winrt::make 함수 템플릿은 추상 가상 함수를 재정의하여 충족시켜야 합니다. 재정의를 제공하는 final
클래스를 사용하여 구현에서 파생하여 이 작업을 수행합니다. 이 프로세스에 대해 관찰해야 할 몇 가지 사항이 있습니다.
첫째, 가상 함수는 디버그 빌드에만 존재합니다. 즉, 감지하는 것은 최적화된 빌드에서 가상 함수 테이블의 크기에 영향을 주지 않습니다.
둘째, winrt::make 사용하는 파생 클래스가 final
있다는 것은, 이전에 구현 클래스를 final
표시하지 않도록 선택한 경우에도 최적화 도구에서 추론할 수 있는 모든 비가상화가 발생할 수 있음을 의미합니다. 그래서 개선입니다. 반대로, 구현 이 될 수 없습니다. 다시 말하지만, 인스턴스화된 형식은 항상 final
이기 때문에 중요하지 않습니다.
셋째, 구현에서 가상 함수를 final
로 표시하는 데 아무런 제약이 없습니다. 물론 C++/WinRT는 클래식 COM 및 모든 구현 요소가 가상인 경향이 있는 WRL과 같은 구현 방식과 크게 다릅니다. C++/WinRT에서 가상 디스패치는 항상 final
ABI(애플리케이션 이진 인터페이스)로 제한되며 구현 메서드는 컴파일 시간 또는 정적 다형성에 의존합니다. 이는 불필요한 런타임 다형성을 방지하고 C++/WinRT 구현에서 가상 함수에 대한 귀중한 이유가 거의 없다는 것을 의미합니다. 이는 매우 좋은 일이며 훨씬 더 예측 가능한 인라인 처리로 이어집니다.
넷째, winrt::make 는 파생 클래스를 삽입하므로 구현에 프라이빗 소멸자가 있을 수 없습니다. 프라이빗 소멸자가 클래식 COM 구현에서 인기가 있었던 이유는 모든 것이 가상이므로 원시 포인터를 직접 처리하는 것이 일반적이므로 릴리스
따라서 C++/WinRT는 클래식 COM 코드를 작성하는 것이 무엇을 의미하는지에 대한 선입견에 도전합니다. WinRT는 클래식 COM이 아니기 때문에 완벽하게 합리적입니다. 클래식 COM은 Windows 런타임의 어셈블리 언어입니다. 매일 작성하는 코드가 되어서는 안 됩니다. 대신 C++/WinRT를 사용하면 최신 C++와 비슷하고 클래식 COM과 훨씬 덜 유사한 코드를 작성할 수 있습니다.
중요 API
관련 항목
- C++/WinRT를 사용하여 API 활용하기
- C++/WinRT 사용하여 작성자 API