C++) ComPtr
오늘의 C++은 사실 100%이해는 하지 못했다.
ComPtr을 경험하기 전까지, 스마트 포인터라는 것을 몰랐고,
C++은 불편한 언어니까 뭐든 해제해주고, 하나부터 열까지 다 지정해줘야 하는 줄 알았다.
헷갈릴 때마다 다시 한 번 들여다 보기 위해, 정리해본다.
ComPtr이란?
- ComPtr은 COM 객체(내가 목표로하는 Direct3D 관련 인터페이스)를 다룰 때, 참조 카운트 관리(Reference Counting)를 자동화해주는 스마트 포인터이다.
- 일반적인 IUnknown 인터페이스 기반의 COM 포인터를 직접 사용하면 AddRef, Release를 직접 호출해줘야 하지만, ComPtr을 사용하면 AddRef, Release 호출이 자동으로 처리되어 메모리 누수 위험을 줄일 수 있다.
- ComPtr은 템플릿 클래스이며, 사용하는 인터페이스 타입에 맞춰 제네릭하게 사용 가능하다.
예) ComPtr
등
필요한 라이브러리 / 헤더
- #include <wrl/client.h>
- ComPtr을 사용하기 위해선 Windows SDK가 설치되어 있어야 하며, wrl/client.h 헤더를 포함해야 한다.
#include <wrl/client.h>
using Microsoft::WRL::ComPtr;
주로 사용하는 프로젝트 예시
- DirectX(Direct3D, Direct2D 등)의 COM 기반 인터페이스를 사용 시 (이 것 제외하고는 나랑은 큰 상관 없을 듯)
- UWP(Universal Windows Platform) 또는 WinRT 기반 앱 개발
- Windows 드라이버나 기타 COM 기반 라이브러리를 사용하는 프로젝트 전반
- COM 기반 객체(Office, Shell 등)를 사용하는 일반 C++ 프로젝트
즉, Windows에서 COM 객체를 다루는 모든 프로젝트에서 유용하게 사용된다.
기본 사용법
선언 및 초기화
#include <wrl/client.h>
using Microsoft::WRL::ComPtr;
ComPtr<IUnknown> pUnknown; // IUnknown 타입을 가리키는 스마트 포인터
기본적으로 AddRef나 Release를 직접 호출할 필요 없이, pUnknown이 스코프를 벗어나면 자동으로 Release가 호출된다.
객체 획득 (GetAddressOf / & 연산자 사용)
COM 함수들은 인터페이스 포인터를 반환할 때, 이중 포인터(**)를 받도록 정의된 경우가 많다. 예를 들어, Direct3D11에서 D3D11CreateDevice 함수
HRESULT D3D11CreateDevice(
IDXGIAdapter* pAdapter,
D3D_DRIVER_TYPE DriverType,
HMODULE Software,
UINT Flags,
const D3D_FEATURE_LEVEL* pFeatureLevels,
UINT FeatureLevels,
UINT SDKVersion,
ID3D11Device** ppDevice, // <- 인터페이스 이중 포인터
D3D_FEATURE_LEVEL* pFeatureLevel,
ID3D11DeviceContext** ppImmediateContext
);
이때 ComPtr
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> deviceContext;
HRESULT hr = D3D11CreateDevice(
nullptr, // IDXGIAdapter*
D3D_DRIVER_TYPE_HARDWARE, // Driver type
nullptr, // Software
0, // Flags
nullptr, // Feature levels
0, // Number of feature levels
D3D11_SDK_VERSION, // SDK Version
device.GetAddressOf(), // ID3D11Device** -> ComPtr에서 제공
nullptr, // 실제 생성된 Feature Level
deviceContext.GetAddressOf() // ID3D11DeviceContext**
);
if (SUCCEEDED(hr))
{
// device, deviceContext를 안전하게 사용 가능
}
- GetAddressOf() 메서드는 ComPtr 내부의 포인터를 이중 포인터 형태로 반환해주기 때문에, 함수에 넘겨줄 수 있다.
- 직접 &device로 넘겨줄 수도 있으나 GetAddressOf()가 권장되며, &device 사용 시에는 더 주의가 필요하다. (내부적으로 포인터가 올바르게 재설정되지 않을 수 있음)
인터페이스 메서드 호출
ComPtr<ID3D11Texture2D> texture2D;
// texture2D가 이미 유효하게 생성되어 있다고 가정
D3D11_TEXTURE2D_DESC desc;
texture2D->GetDesc(&desc); // 스마트 포인터지만 실제 사용 시에는 원본 인터페이스처럼 '->' 사용
- ComPtr도 operator->가 오버로딩되어 있어, 일반 포인터와 유사하게 사용 가능.
리셋(Reset) & 재할당
device.Reset(); // 내부 참조 해제(Release)가 일어남
// 새로운 인터페이스 포인터 설정도 가능
device = anotherComPtr;
- 기존에 참조하던 COM 객체를 해제하고 싶으면 Reset() 메서드를 사용한다.
- 새로운 ComPtr 객체로 대입(=)하면 내부 참조도 함께 이동한다.
정리
- ComPtr은 Windows COM 객체를 안전하고 편리하게 관리하기 위한 스마트 포인터다.
- 참조 카운팅과 리소스 해제를 자동으로 처리하므로, 직접 AddRef / Release를 호출할 필요가 없어 코드가 간결해지고 메모리 누수를 예방한다.
- 사용을 위해서는 #include <wrl/client.h>를 포함하고, using namespace Microsoft::WRL를 사용하면 된다.
- DirectX 등 기타 COM 기반 Windows API를 사용하는 프로젝트에서 매우 유용.
만약 COM 프로그래밍을 한다면, ComPtr은 사실상 필수적으로 사용되는 스마트 포인터인것 같다.
(편하고, 긴장의 끈을 약간은 놓아도 된다)
댓글남기기