티스토리 뷰
[Effective Modern C++] 항목 7. 객체 생성 시 괄호(())와 중괄호({})를 구분하라
pppgod 2019. 9. 21. 21:13객체를 생성하는 방법은 생각보다 다양하다. 아래와 같은 방법이 존재한다.
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개의 원소
v1과 v2는 똑같은 파라미터를 사용하고 있지만 그 결과는 아주 다르다. 두 방법의 차이를 익히고 적재적소에 사용하는 것이 좋겠다.
참고 서적
스콧 마이어스, Effective Modern C++
'C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 항목 10. 범위 없는 enum보다 범위 있는 enum을 선호하라 (0) | 2019.09.28 |
---|---|
[Effective Modern C++] 항목 9. typedef보다 별칭 선언을 선호하라 (0) | 2019.09.28 |
[Effective Modern C++] 항목 6. auto가 원치 않은 형식으로 연역될 때에는 명시적 형식의 초기치를 사용하라 (0) | 2019.09.08 |
[Effective Modern C++] 항목 5. 명시적 형식 선언보다는 auto를 선호하라 (0) | 2019.09.08 |
[Effective Modern C++] 항목 4. 연역된 형식을 파악하는 방법을 알아두라 (0) | 2019.09.02 |
- Total
- Today
- Yesterday
- 다이소
- std::forward
- std::move
- C++11
- detach
- Forwarding
- Future
- Overloading
- Override
- 보편 참조
- Effective Modern C++
- 포인터
- 람다
- auto
- MOVE
- thread
- 발아시기
- async
- C++14
- CPP
- const
- Perfect
- C
- forward
- Effective
- C++
- Modern
- 보편참조
- Join
- Unreal
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |