본문 바로가기
C++

new 할당과 초기화

by Oz Driver 2026. 1. 9.

1. ( ) 소괄호를 사용한 초기화

new T(args) 형태로 사용하는 전통적인 방식입니다. 2010년 이전부터 사용되던 방식이며, 기본적으로 인수가 있는 생성자를 호출할 때 사용됩니다.

 

 •  사용 예시 : new int(10), new Player("Warrior", 55)

 •  빈 초기화 : new int() 처럼 인수를 비워두면 0으로 값이 초기화됩니다.

 •  단점 (안정성) : 소괄호 방식은 int i = 3.14;처럼 정밀도 손실이 발생하는 "좁아지는 변환(Narrowing Conversion)" 을 허용합니다. 이는 잠재적인 버그의 원인이 될 수 있습니다.

 

2. { } 중괄호를 사용한 초기화 

new T{args} 형태로 사용하는 모던 C++ (C++11 이후) 방식입니다. 유니폼 초기화라고 불리며, 모든 종류의 초기화(동적 할당, 정적 할당, 멤버 초기화 등) 에 일관되게 사용하도록 설계되었습니다.

 

 •  사용 예시 : new int{10}, new Player{"Mage", 60}

 •  빈 초기화 : new int{}처럼 비워두면 new int()와 마찬가지로 0으로 값이 초기화됩니다. 이 방식이 더 권장됩니다.

 •  가장 큰 장점 (안정성) : 중괄호 {} 초기화는 좁아지는 변환을 컴파일 시점에서 엄격하게 차단합니다. 예를 들어, new int{3.14} 를 시도하면 컴파일 오류가 발생하여 데이터 손실 버그를 사전에 방지할 수 있습니다.

 

3. 클래스 객체 초기화의 공통점

클래스나 구조체(Player 타입)를 초기화할 때는 new Player("Warrior", 55)든 new Player{"Mage", 60}든, 인수에 맞는 생성자가 클래스에 정의되어 있어야 한다는 기본 원칙은 같습니다. 두 방식 모두 정의된 생성자를 호출합니다.

다만, 중괄호 {} 는 생성자가 없는 아주 구조체나 클래스의 경우에만 멤버를 순서대로 채워주는 집단 초기화라는 특별한 기능을 수행합니다.

struct Position 
{ 
    int x; 
    double y; 
};

// Position 에 기본 생성자가 없는 경우,
// x에 10을, y에 5.5를 자동으로 넣어줍니다.
new Position{10, 5.5};

 

4. 코드 예시

기존의 괄호 () 방식과 모던 C++ (C++11 이후) 의 중괄호 { } 방식의 new 할당 및 초기화하는 코드를 예제로 살펴보겠습니다.

#include <iostream>
#include <string>
#include <memory> // 스마트 포인터를 위해 필요

// 예제용 클래스 정의
class Player {
public:
    std::string name;
    int level;

    // 생성자
    Player(std::string n, int l) : name{n}, level{l} 
    {
        std::cout << "[생성] Player 객체 생성됨: " << name << std::endl;
    }

    void printInfo() 
    {
        std::cout << "이름: " << name << ", 레벨: " << level << std::endl;
    }
};

int main() {
    std::cout << "=== 1. 기본 자료형(Primitive Types) 초기화 ===" << std::endl;

    // 1-1. 초기화 없이 할당 (쓰레기 값이 들어있을 수 있음)
    int* ptr1 = new int; 
    *ptr1 = 10; // 나중에 값 대입
    std::cout << "ptr1 값: " << *ptr1 << std::endl;

    // 1-2. 직접 초기화 (괄호 () 사용) - 가장 일반적
    int* ptr2 = new int(20);
    std::cout << "ptr2 값: " << *ptr2 << std::endl;

    // 1-3. 유니폼 초기화 (중괄호 {} 사용, C++11 이상)
    int* ptr3 = new int{30};
    std::cout << "ptr3 값: " << *ptr3 << std::endl;

    // 1-4. 값 초기화 (빈 괄호 () 또는 중괄호 {} 사용 -> 0으로 초기화됨)
    int* ptrZeroA = new int(); // 전통적인 방식: 0으로 초기화
    int* ptrZeroB = new int{}; // 모던 C++ 방식: 0으로 초기화
    std::cout << "ptrZeroA 값(0 초기화): " << *ptrZeroA << std::endl;
    std::cout << "ptrZeroB 값(0 초기화): " << *ptrZeroB << std::endl;


    std::cout << "\n=== 2. 배열(Array) 초기화 ===" << std::endl;

    // 2-1. 초기화 없이 배열 할당 (각 요소는 쓰레기 값이 들어있을 수 있음)
    int* arr1 = new int[3];
    arr1[0] = 100; arr1[1] = 200; arr1[2] = 300;
    std::cout << "arr1[1]: " << arr1[1] << std::endl;

    // 2-2. 모든 요소를 0으로 초기화 (두 방식 모두 사용 가능)
    int* arrZeroA = new int[5](); // 전통적인 방식: 모든 요소 0으로 초기화
    int* arrZeroB = new int[5]{}; // 모던 C++ 방식: 모든 요소 0으로 초기화
    std::cout << "arrZeroA[3] (0이어야 함): " << arrZeroA[3] << std::endl;
    std::cout << "arrZeroB[3] (0이어야 함): " << arrZeroB[3] << std::endl;

    // 2-3. 특정 값으로 초기화 (C++11 이상)
    // 크기가 3인 배열을 만들고 1, 2, 3으로 초기화
    int* arrInit = new int[3]{1, 2, 3};
    std::cout << "arrInit[2]: " << arrInit[2] << std::endl;


    std::cout << "\n=== 3. 객체(Object) 초기화 ===" << std::endl;

    // 생성자를 호출하여 초기화
    // 참고: 클래스/객체는 ( ) 또는 { } 모두 인수에 맞는 생성자가 정의되어 있어야 함.
    Player* p1 = new Player("Warrior", 55);
    p1->printInfo();

    // C++11 유니폼 초기화 스타일
    // (1) 현재 Player처럼 '생성자가 있는 경우', 해당 생성자를 호출함.
    // (2) 생성자가 없는 'Aggregate' 타입인 경우, 내부 멤버를 순서대로 채움 (집단 초기화).
    Player* p2 = new Player{"Mage", 60};
    p2->printInfo();


    std::cout << "\n=== 4. 중요: 메모리 해제 (delete) ===" << std::endl;
    // new로 할당한 메모리는 반드시 delete로 해제해야 메모리 누수(Memory Leak)를 방지합니다.

    // 단일 변수/객체 해제
    delete ptr1;
    delete ptr2;
    delete ptr3;
    delete ptrZeroA;
    delete ptrZeroB;
    delete p1;
    delete p2;

    // 배열 해제 (반드시 delete[] 를 사용해야 함)
    delete[] arr1;
    delete[] arrZeroA;
    delete[] arrZeroB;
    delete[] arrInit;

    std::cout << "모든 raw 포인터 메모리 해제 완료." << std::endl;


    std::cout << "\n=== 5. 모던 C++ 추천 방식 (스마트 포인터) ===" << std::endl;
    // 최근 C++에서는 new/delete를 직접 쓰기보다 스마트 포인터를 권장합니다.
    // 자동으로 메모리를 해제해줍니다.

    // std::make_unique<타입>(생성자 인자)
    // 참고: make_unique는 함수 호출이므로 ( )를 사용해야 하며, 내부적으로도 new T(args) 방식을 사용합니다.
    std::unique_ptr<int> smartInt = std::make_unique<int>(999);
    std::cout << "스마트 포인터 int 값: " << *smartInt << std::endl;

    std::unique_ptr<Player> smartPlayer = std::make_unique<Player>("Archer", 10);
    smartPlayer->printInfo();

    // delete를 호출할 필요가 없음 (함수 종료 시 자동 해제)

    return 0;
}