오늘의 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은 사실상 필수적으로 사용되는 스마트 포인터인것 같다.

(편하고, 긴장의 끈을 약간은 놓아도 된다)

태그:

카테고리:

업데이트:

댓글남기기