티스토리 뷰

 객체를 생성하는 방법은 생각보다 다양하다. 아래와 같은 방법이 존재한다.

int x(0);
int y = 0;
int z{ 0 };
int z = { 0 };

 위의 4가지 방법 모두 똑같은 결과를 나타낸다. 그 중에서 중괄호({})로 나타낸 생성 방법을 바로 균일 초기화(uniform initialization)이라고 한다.

균일 초기화

도입 배경

 균일 초기화라고 부르는 이유는 어떤 상황에서든 균일하게 초기화를 할 수 있기 때문이다. 균일 초기화를 도입하게 된 3가지 배경을 먼저 알아보자.

컨테이너 초기화

vector와 같이 배열의 경우 초기 값들을 지정할 수 있다.

std::vector<int> v{ 1, 3, 5 };  // v의 원소는 1, 3, 5

멤버 초기화

non-static 자료 멤버를 초기화 하는데 사용할 수 있다. 소괄호의 경우에는 컴파일 에러가 난다.

class Widget {
   ...
   private:
   int x{ 0 };
   int y = 0;
   int z(0);    // error
}

복사 불가 객체 초기화

복사 불가능한 객체 또한 초기화가 가능하다.

std::atomic<int> ai1{ 0 };
std::atomic<int> ai2(0);
std::atomic<int> ai3 = 0;   // error

장점

narrowing conversion 방지

 균일 초기화의 장점중 하나는 좁히기 변환(narrowing conversion)을 방지해준다. 좁히기 변환이 무엇인지 먼저 알아보자.

double x, y, z;
int sum1(x + y + z};
int sum2 = x + y + z;

위의 결과는 모두가 알다시피 double 에서 int 로 변환을 해준다. 그렇기 때문에 실수로 인해 발생하는 오류를 찾기 힘들어질수도 있다. 하지만 균일 초기화를 사용하면 이런 문제가 없다.

double x, y, z;
int sum1{ x + y + z };  // error

컴파일러가 에러를 보내 이런 실수가 발생하는 것을 방지해준다.

most vexing parse에 자유로움

c++에는 가장 성가신 구문 해석(most vexing parse)라는 것이 있다. 인수가 없는 생성자를 호출하려고 할 때 컴파일러가 함수명인지 변수명 구분하기가 어려워 발생한다.

Widget w1();  // w1이라는 함수 선언
Widget w2{};  // 인수 없는 생성자 호출

위 예제에서 보듯이 균일 초기화에서는 문제없이 생성자를 호출해준다.

단점

 위와 같은 장점들이 있지만 단점 또한 존재한다.

std::initializer_list 문제

생성자 중에 std::initailizer_list 를 파라미터로 사용하는 경우에 예상치 못한 문제가 발생한다. 

class Widget {
public:
    Widget(int i, bool b);
    Widget(int i, double d);
    Widget(std::initializer_list<long double> il);
}

Widget w1(10, true);    // Widget(int i, bool b) 호출
Widget w1{10, true};    // Widget(std::initializer_list<long double> il) 호출
Widget w1(10, 5.0);     // Widget(int i, double d) 호출
Widget w1{10, 5.0};     // Widget(std::initializer_list<long double> il) 호출

명백히 타입이 맞지 않음에도 long double 로 변환되어서 호출한다.

물론 회피 가능하도록 작성이 가능하다.

class Widget {
public:
    Widget(int i, bool b);
    Widget(int i, double d);
    Widget(std::initializer_list<std::string> il);
}

Widget w1(10, true);    // Widget(int i, bool b) 호출
Widget w1{10, true};    // Widget(int i, bool b) 호출
Widget w1(10, 5.0);     // Widget(int i, double d) 호출
Widget w1{10, 5.0};     // Widget(int i, double d) 호출

위와 같이 narrowing conversion을 할 수 없는 경우(int -> string) 에는 의도한대로 잘 동작한다.

컨테이너 초기화

std::initializer_list는 컨테이너를 초기화하는데 사용한다. 그런데 만약 빈 컨테이너를 만들고 싶다면 어떻게 해야할까?

Widget w1{{}};
Widget w2({});

위와 같이 중괄호를 괄호로 감싸서 선언해주기만 하면된다. 

마무리

 앞서 살펴봤듯이 균일 초기화를 항상 써야하는 것은 아니다. 적절한 상황에 맞춰 사용하는 것이 좋다. 대표적으로 vector 객체를 생성해보자.

std::vector<int> v1(10, 20);  // 20으로 초기화된 10개의 원소
std::vector<int> v2{10, 20};  // 2개의 원소

v1v2는 똑같은 파라미터를 사용하고 있지만 그 결과는 아주 다르다. 두 방법의 차이를 익히고 적재적소에 사용하는 것이 좋겠다.

 

참고 서적

스콧 마이어스, Effective Modern C++
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함