placement new는 왜 필요할까?

//
//

들어가며

이전 글에서는 allocator를 사용해 직접 구현한 Vector를 개선해봤습니다.

그 과정에서 allocator는 단순 메모리 할당기가 아니라,
메모리 확보와 객체 생명주기를 분리하기 위한 구조라는 이야기를 했습니다.

그리고 allocator를 사용하다 보면 자연스럽게 이런 코드가 등장합니다.

allocator.construct(ptr, value);

내부적으로는

new (ptr) T(value);

같은 코드가 사용됩니다.

처음 보면 꽤 이상하게 느껴집니다. new 앞에 왜 괄호가 붙어있지?

혹은 이미 메모리가 있는데 왜 또 new를 호출하는 걸까?

이번 글에서는

  • placement new가 왜 필요한지
  • 일반 new와 무엇이 다른지
  • allocator와 어떤 관계가 있는지
  • std::vector 내부 구조와 어떻게 연결되는지

를 중심으로 정리해보겠습니다.


일반 new는 무엇을 할까?

먼저 일반적인 new를 생각해보겠습니다.

Player* player = new Player();

이 코드는 단순히 객체를 생성하는 것처럼 보입니다.

하지만 실제로는 두 가지 작업이 동시에 발생합니다.

  1. 메모리 확보
  2. 해당 위치에 객체 생성

즉, 아래 코드를 실행하면 Player 클래스의 생성자 호출도 함께 일어납니다.

operator new(sizeof(Player));

이 점이 중요합니다.

C++의 일반 new는 메모리 확보와 객체 생성을 동시에 수행합니다.


//

그런데 std::vector는 조금 다르다

이전 글에서 allocator 기반 Vector를 구현할 때 이런 이야기를 했습니다.

  • capacity만큼 메모리 확보
  • size만큼만 실제 객체 생성

std::vector는 보통

  • 메모리는 미리 크게 확보
  • 실제 객체는 필요할 때만 생성

하는 구조를 사용합니다.

예를 들어

size = 3
capacity = 8

이라면, 메모리는 8칸 확보되어 있지만, 실제로 객체는 3개만 존재합니다.

아직 객체가 없는 raw memory 공간이 존재합니다.


raw memory란?

raw memory는 말 그대로 메모리만 존재하고 아직 객체는 없는 상태입니다.

예를 들어, allocator는

allocator.allocate(8);

를 호출하면 메모리만 확보합니다.

[ 메모리 확보 완료 ]
[ 아직 객체 없음 ]

상태입니다.

이 상태에서는 생성자도 호출되지 않았고, 객체도 존재하지 않습니다.

단순 바이트 공간만 확보된 상태입니다.


그런데 객체를 생성하려면?

문제는 여기서 발생합니다.

raw memory는 단순 메모리일 뿐입니다.

따라서 아래와 같은 코드를 바로 사용하면 안 됩니다.

data[size] = value;

왜냐하면 그 위치에는 아직 객체가 존재하지 않기 때문입니다.

대입 연산자를 호출하려 해도, 애초에 객체 자체가 없는 상태입니다.

그래서 확보한 메모리 위치에 직접 객체를 생성할 방법이 필요합니다.

그게 바로 placement new입니다.


placement new란?

지금까지 설명한 일반 new와 placement new의 차이, 그리고 allocator 기반 객체 생성 구조를 그림으로 정리해보면 다음과 같습니다.

핵심은 placement new는 메모리를 새로 할당하는 것이 아니라, 이미 확보된 raw memory 위치에 객체를 생성하는 구조라는 점입니다.

C++ placement new와 raw memory, allocator 기반 객체 생성 구조를 설명하는 인포그래픽
일반 new와 placement new의 차이, raw memory와 allocator 기반 객체 생성 구조를 정리한 이미지입니다.

placement new는 이미 확보된 메모리 위치에 객체를 생성하는 문법입니다.

위 구조처럼 placement new는

  • 메모리 확보와 객체 생성 분리
  • raw memory 기반 객체 관리
  • 필요한 위치에만 객체 생성
  • 메모리 재사용 구조 구현

를 가능하게 합니다.

이 방식은 std::vector, allocator, 게임 엔진 메모리 시스템의 핵심 구조와도 연결됩니다.

형태는 다음과 같습니다.

new (memoryAddress) T(args);

사용 방법은 아래 코드와 같습니다.

Player* player = new (buffer) Player();

여기서 중요한 점은 이 코드는 메모리를 새로 할당하지 않는다는 것입니다.

이미 존재하는 buffer 위치에 Player 객체의 생성자를 호출합니다.

  • 메모리 확보 X
  • 객체 생성만 수행

합니다.


일반 new와 placement new 차이

둘의 차이를 비교해보면 다음과 같습니다.


일반 new

Player* p = new Player();

내부적으로 메모리 확보 + 생성자 호출 둘 다 수행합니다.


placement new

new (buffer) Player();

내부적으로 이미 확보된 메모리 사용 + 생성자 호출만 수행합니다.

즉, placement new는 객체 생성만 담당합니다.


allocator.construct도 결국 placement new다

이전 글에서 allocator를 사용할 때 아래 코드를 사용했습니다.

allocator.construct(data + size, value);

사실 allocator 내부에서는 대체로 placement new를 사용합니다.

아래 코드와 비슷한 개념입니다.

new (data + size) T(value);

allocator의 핵심 구조는

  • allocate() -> Raw Memory 확보
  • Placement new -> 필요한 위치에 객체 생성

흐름이라고 볼 수 있습니다.


실제 예제

간단한 placement new 예제를 보겠습니다.

#include <iostream>
#include <new>

class Player
{
public:
    Player()
    {
        std::cout << "Player Constructor\n";
    }

    ~Player()
    {
        std::cout << "Player Destructor\n";
    }
};

int main()
{
    char buffer[sizeof(Player)];

    Player* player = new (buffer) Player();

    player->~Player();
}

이 코드에서는

  • stack 메모리 buffer 확보
  • 그 위치에 Player 생성
  • 직접 소멸자 호출

을 수행합니다.

여기서 중요한 점은 delete를 호출하지 않는다는 것입니다.

왜냐하면, 메모리는 stack에 존재하기 때문입니다.

placement new는 메모리를 할당하지 않았으므로, 메모리 해제도 하지 않습니다.


placement new는 destroy를 직접 해야 한다

일반 new는 아래 코드와 같이 delete를 호출하면 소멸자 호출 + 메모리 해제를 모두 처리합니다.

delete player;

하지만, placement new는 다릅니다.

메모리는 별도로 관리되기 때문에 소멸자도 직접 호출해야 합니다.

따라서 아래 코드와 같이 직접 소멸자를 호출해줘야 합니다.

player->~Player();

이 부분은 처음 보면 꽤 낯설 수 있습니다.

하지만, allocator 구조에서는 매우 중요한 개념입니다.


왜 게임 엔진에서 중요할까?

그렇다면 이 개념이 게임 엔진을 이해하는데 왜 중요할까요?

게임 엔진은

  • Pool Allocator
  • Frame Allocator
  • Custom Arena

같은 구조를 자주 사용합니다.

큰 메모리 블록을 미리 확보해두고, 그 안에서 객체를 생성/파괴합니다.

이때 필요한 것이 바로 placement new입니다.

아래와 같은 단계로 메모리를 관리합니다.

미리 확보한 메모리 Pool

필요한 위치에 객체 생성

사용 종료 시 destroy

메모리 재사용

즉, placement new는 현대 게임 엔진 메모리 시스템의 핵심 개념 중 하나입니다.


placement new가 중요한 진짜 이유

처음 placement new를 보면 단순히 “이상한 문법”처럼 보일 수 있습니다.

하지만 실제 핵심은 이것입니다.

메모리와 객체 생명주기를 분리하기 위한 구조

즉,

  • 메모리는 미리 확보
  • 객체는 필요할 때 생성
  • 객체만 제거 후 메모리는 재사용

같은 구조를 만들 수 있습니다.

이건

  • std::vector
  • allocator
  • 게임 엔진 메모리 시스템

모두와 연결됩니다.


std::vector와 다시 연결해보면

이전 allocator 기반 Vector 코드에서 아래 코드는 raw memory 확보하고,

allocator.allocate(newCapacity);

그리고 아래 코드는 placement new를 기반으로 객체를 생성합니다.

allocator.construct(newBlock + ix, value);

즉, std::vector는 사실상 raw memory 관리 + placement new 기반 객체 생성 구조라고 볼 수 있습니다.


핵심 정리

  • 일반 new는 메모리 확보와 객체 생성을 동시에 수행한다
  • std::vector는 capacity와 size를 분리해서 관리한다
  • allocator.allocate()는 raw memory만 확보한다
  • raw memory에는 아직 객체가 존재하지 않는다
  • placement new는 이미 확보된 메모리 위치에 객체를 생성한다
  • allocator.construct()는 내부적으로 placement new와 연결된다
  • placement new는 메모리를 해제하지 않으므로 destroy를 직접 호출해야 한다
  • 게임 엔진 메모리 시스템에서도 placement new는 매우 중요하다

마무리

placement new는 처음 보면 꽤 낯선 문법입니다.

하지만 allocator와 std::vector 구조를 이해하기 시작하면 왜 이런 문법이 필요한지 자연스럽게 연결됩니다.

핵심은 메모리와 객체 생명주기를 분리해서 관리하는 것입니다.

그리고 이 구조는

  • STL 컨테이너
  • allocator
  • 게임 엔진 메모리 시스템
  • custom pool allocator

모두와 이어집니다.

placement new는 단순 문법이 아니라, 현대 C++ 메모리 관리 구조를 이해하기 위한 중요한 연결 지점이라고 볼 수 있습니다.

👉 게임 엔진 구조를 더 깊이 이해하고 싶다면

아래 강의를 통해 직접 구현해보는 것을 추천드립니다.

C++로 만드는 게임 엔진 프레임워크 강의

👉 C++로 만드는 게임 엔진 프레임워크 강의 바로가기

//
   

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다