[Modern C++] 항목 7: 객체 생성 시 괄호(())와 중괄호({})를 구분하라
카테고리: Cpp
이 글은 아래의 책을 정리하였습니다. 이펙티브 모던 C++, 스콧 마이어스 저자, 류광 번역
📦 3. 현대적 C++에 적응하기
👉🏻 항목 7: 객체 생성 시 괄호(())와 중괄호({})를 구분하라
int x(0); // 소괄호
int y = 0; // 등호
// 아래 두 구문은 동일하게 취급된다.
int z{ 0 }; // 중괄호
int z = { 0 }; // 중괄호 + 등호
- C++11에서 객체 생성 구문이 다양해졌다.
Widget w1; // 기본 생성자 호출
Widget w2 = w1; // 복사 생성자 호출
w1 = w2; // 복사 대입 생성자 호출
- 초기화와 대입은 각자 다른 함수를 호출한다.
=을 사용하면, 대입이 일어나지 않는다.
📌 균일 초기화 (Uniform Initialization)
- 여기선 중괄호 초기화라는 표현을 사용한다.
vector<int> v{ 1, 3, 5 };처럼 사용할 수 있다.
✅ 특징 1: 어디서나 사용 가능
// 비정적 자료 멤버 초기화의 경우
class Widget {
private:
int x{ 0 }; // ✅
int y = 0; // ✅
int z(0); // ⚠️ 오류!
};
// 복사할 수 없는 객체(atomic)의 경우
atomic<int> ai1{ 0 }; // ✅
atomic<int> ai2(0); // ✅
atomic<int> ai3 = 0; // ⚠️ 오류!
- 복사할 수 없는 객체는 소괄호/중괄호로는 초기화할 수 있지만,
=로는 초기화할 수 없다.
✅ 특징 2: 축소 변환(Narrowing Conversion) 방지
double x, y, z;
// ⚠️ 오류!
// double → int로의 축소 변환 불가!
int sum1{ x + y + z };
int sum2( x + y + z ); // ✅ double → int
int sum3 = x + y + z; // ✅ 동일
- 암묵적 축소 변환을 방지해준다.
- 소괄호와 등호를 사용한 초기화는 축소 변환을 점검하지 않는다.
✅ 특징 3: 가장 성가신 구문 해석(Most Vexing Parse) 회피
// int를 받는 생성자 호출
Widget w1(10);
// ⚠️ 가장 성가신 구문 해석!
// Widget을 반환하는 w2 함수 선언
Widget w2();
// 인수 없는 생성자 호출
Widget w3{};
- 가장 성가신 구문 해석: 선언으로 해석할 수 있는 것은 항상 선언으로 해석해야 한다는 부작용이다.
- 중괄호 초기화는 해당 부작용에서 자유롭다.
⚠️ 특징 4: 오버로딩 우선순위
class Widget {
public:
Widget(int i, bool b); // ①
Widget(int i, double d); // ②
};
Widget w1(10, true); // ① 호출
Widget w2{10, true}; // ① 호출
Widget w3(10, 5.0); // ② 호출
Widget w4{10, 5.0}; // ② 호출
- 평범한 코드이며, 흐름도 예상대로 흘러간다.
class Widget {
public:
Widget(int i, bool b); // ①
Widget(int i, double d); // ②
Widget(std::initializer_list<long double> il); // ③
operator float() const;
};
Widget w1(10, true); // ① 호출
Widget w2{10, true}; // ③ 호출 (10, true가 long double로 변환)
Widget w3(10, 5.0); // ② 호출
Widget w4{10, 5.0}; // ③ 호출 (10, 5.0이 long double로 변환)
Widget w5(w4); // 복사 생성자 호출
Widget w6{w4}; // ③ 호출 (w4 → float → long double 변환)
Widget w7(std::move(w4)); // 이동 생성자 호출
Widget w8{std::move(w4)}; // ③ 호출 (w4 → float → long double 변환)
std::initializer_list형식 매개변수가 있는 생성자가 추가되면, 달라진다.- 중괄호 초기화 사용 시, 해당 생성자를 오버로딩 최우선 순위로 잡는다!
- 복사/이동 생성자가 호출되어야 할 상황에서도, 가로채는 일이 발생한다!
class Widget {
public:
Widget(int i, bool b); // ①
Widget(int i, double d); // ②
Widget(std::initializer_list<bool> il); // ③
};
// ⚠️ 컴파일 오류!
// int, double → bool로의 축소 변환 방지됨!
Widget w{10, 5.0};
- ② 생성자가 있음에도, ③ 생성자가 채간다.
- 이로 인해, 오류가 발생할 수 있다.
class Widget {
public:
Widget(int i, bool b); // ①
Widget(int i, double d); // ②
Widget(std::initializer_list<string> il); // ③
};
Widget w1(10, true); // ① 호출
Widget w2{10, true}; // ① 호출
Widget w3(10, 5.0); // ② 호출
Widget w4{10, 5.0}; // ② 호출
- 아예 변환이 불가능한 경우, 생성자를 가로채는 일이 발생하지 않는다.
class Widget {
public:
Widget();
Widget(std::initializer_list<int> il); // ③
};
Widget w1; // 기본 생성자 호출
Widget w2{}; // 기본 생성자 호출
Widget w3(); // ⚠️ 가장 성가신 구문 해석으로 인한 함수 선언
Widget w4({}); // ③ 호출
Widget w5{()}; // ③ 호출
- 빈 중괄호 쌍은 인수 없음을 뜻하며, 기본 생성자를 호출한다.
- 빈
std::initializer_list로 ③ 생성자를 호출하기 위해:({}),{()}형태로 사용한다.
💡 알아두어야 할 점
- 오버로딩된 생성자 중
std::initializer_list를 받는 함수가 하나 이상 존재하면, 중괄호 초기화 구문을 이용하는 코드에 해당 함수만 적용될 수 있다는 것을 주의하자. - 생성자를 설계할 때, 소괄호/중괄호에 따라 다른 오버로딩 버전이 선택되지 않도록 하자.
- e.g.
vector<int> v1(10, 20)과vector<int> v2{10, 20}의 결과가 다르며, 이는 설계의 오류이다.
- e.g.
- 클래스를 사용할 때, 소괄호 혹은 중괄호를 세심하게 선택하자.
- 둘 중 하나를 선택해, 일관되게 적용하자.
template<typename T, typename... Ts>
void doSomeWork(Ts&&... params) {
// ① 소괄호 사용하는 경우
T localObject(std::forward<Ts>(params)...);
// ② 중괄호 사용하는 경우
T localObject{std::forward<Ts>(params)...};
...
}
vector<int> v;
doSomeWork<vector<int>>(10, 20); // localObject: 요소가 10개인 vector
doSomeWork<vector<int>>{10, 20}; // localObject: 요소가 2개인 vector
- 템플릿 작성자는 소괄호/중괄호 중 하나를 선택한다.
- 사용자는 소괄호/중괄호 중 무엇을 사용해야 하는지 알 수 없다.
- 그러므로 인터페이스 일부에 문서화하여 문제를 해결해야 한다.
- 이는
make_unique,make_shared(항목 21 참고)에서 해결해야 했던 문제와 동일하다.
- 이는
🧐 정리
- 중괄호 초기화는:
- 광범위하게 사용 가능한 초기화 구문
- 축소 변환 방지
- 가장 성가신 구문 해석에서 자유로움
- 생성자 오버로딩 처리 과정에서 중괄호 초기화는 가능한
std::initializer_list매개변수가 있는 생성자와 부합한다. - 소괄호와 중괄호의 선택이 의미 있는 차이를 만드는 예는 인수 두 개로
vector<형식>을 생성하는 것이다. - 템플릿 안에서 객체를 생성할 때 소괄호/중괄호 중 무엇을 사용할지 선택하기 어려울 수 있다.
댓글남기기