c++ 유니폼 초기화

2 분 소요

유니폼 초기화

C++11 이전에는 타입의 초기화 방식이 일정하지 않았다. 예를 들어 다음과 같이 원을 정의할 때 한 번은 구조체로, 한 번은 클래스로 작성한 경우를 살펴보자
#include <iostream>

struct CircleStruct
{
    int x, y;
    double radius;
};
class CircleClass
{
    public:
    CircleClass(int x, int y, double radius)
    : mX(x), mY(y), mRadius(radius)
    {
        
    }

    private:
    int mX, mY;
    double mRadius;
};

int main(int argc, char *argv[])
{
    CircleStruct mCircie1 = {10, 10, 2.5};
    CircleClass mCircle2(10, 10, 2.5);
    return 0;
}
구조체에 대해서는 {...} 문법을 적용한 반면, 클래스에 대해서는 함수 표기법인 (...)로 생성자를 호출했다. C++11부터 타입을 초기화할 때 {...} 문법을 사용하는 유니폼 초기화(uniform Initialization - 균일 초기화, 중괄호 초기화)를 따르도록 통일됐다. 그렇다면! 이렇게 가능하다는 말이다.
CircleStruct myCircle3 = {10, 10, 2.5};
CircleClass myCircle4 = {10, 10, 2.5};
myCircle4를 정의하는 문장이 실행 될 때 CircleClass의 생성자가 자동으로 호출된다. 물론 등호를 생략해도 좋다!
CircleStruct myCircle3{10, 10, 2.5};
CircleClass myCircle4{10, 10, 2.5};

C++ 모든 대상

이러한 uniform initializer는 구조체나 클래스뿐만 아니라 C++의 모든 대상을 초기화 하는데 사용 될 수 있다. 물론 유니폼 초기화로 영 초기화(zero initialization)를 할 때도 적용 가능하다. 아래와 같다.
int a = 3;
int b(3);
int c = {3};    // uniform initialization, 복제 초기화
int d{3};       // uniform initialization, 직접 초기화
int e{};        // zero initialization

축소 변환

유니폼 초기화를 사용하면 축소 변환 (narrow - 좁히기)을 방지할 수 있다. C++ 에서는 암묵적으로 축소 변활될 때가 있는데, 예를 들면 다음과 같다.
#include <iostream>

void func(int i) {/* ... */}

int main(int argc, char *argv[])
{
    int x = 1.2345;
    func(1.1234);
    return 0;
}
x에 값을 대입할 때와 func()를 호출할 때 전달한 1.234는 3으로 값이 줄어든다. 물론 컴파일러가 요놈을 잡았다! 라고 알려줄 경고 메세지를 날려 주기도 한다.
/Users/colson/workspace/colson-log/c,c++/uniform initialization/ex2.cpp:18:14: warning: implicit conversion from 'double' to 'int' changes value from 1.2345 to 1 [-Wliteral-conversion] int x = 1.2345; ~ ^~~~~~ /Users/colson/workspace/colson-log/c,c++/uniform initialization/ex2.cpp:19:11: warning: implicit conversion from 'double' to 'int' changes value from 1.1234 to 1 [-Wliteral-conversion] func(1.1234); ~~~~ ^~~~~~ 2 warnings generated.
그렇다면 유니폼 초기화를 사용하여 값을 대입하거나 함수를 호출할 때는 어떻게 될까? 답은 아래와 같다.
유니폼 초기화를 사용하는 코드는 축소로 인한 변환이 있으면 반드시 에러를 발생시킨다.

동적 배열 초기화

동적으로 할당되는 배열을 초기화할 때도 적용할 수 있다.
int *pArray = new int[4]{0, 1, 2, 3};
또한 클래스 멤버인 배열을 생성자 이니셜라이저로 초기화할 때도 사용될 수 있다.
class Myclass
{
    public:
        Myclass() : mArray{1, 3, 4, 5} {}
    private:
        int mArray[4];
};

리스트 초기화

이니셜라이져는 두가지가 있다. 이니셜라이져 리스트를 중괄호로 묵어서 표현한다.
  • 복제 리스트 초기화(copy list initialization): obj = {arg1, arg2, ...};
  • 직접 리스트 초기화(direct list initialization): T obj {arg1, arg2, ...};
C++17부터 auto 타입 추론 기능과 관련하여 복제 리스트 초기화의 직접 리스트 초기화가 크게 달라졌다. C++17 이전에는 복제 리스트 초기화의 직접 리스트 초기화 모두 initializer_list<>로 처리했다. C++17부터 auto는 직접 리스트 초기화에 대해 값 하나만 추론한다. 따라서 이 코도를 실행하면 에러가 발생한다. (C++11, C++14)는 문제 없당.
int main(int argc, char *argv[])
{
    // copy list initialization
    auto a = {11};          // C++11,C++14,C++17: initialization_list&lt;&gt;
    auto b = {1234, 434};   // C++11,C++14,C++17: initialization_list&lt;&gt;

    // direct list initialization
    auto c {11};            //  C++11,C++14: initialization_list&lt;&gt;, C++17: int 로 초기화
    auto d {123, 321};      //  C++11,C++14: initialization_list&lt;&gt;, C++17: Initializer for variable 'd' with type 'auto' contains multiple expressions
}
NOTE_ 복제 리스트 초기화에서 중괄호 안에 나온 원소는 반드시 타입이 모두 같아야 한다. auto b = {11, 1.1234}과 같은 코드를 작성하면 에러가 뿜뿜한다.

참고

태그: ,

카테고리:

업데이트: