항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자
단순 선언 시, 객체의 값을 초기화하는 데 있어서 C++은 항상 초기화를 보장하지는 않습니다.
1
2
3
4
5
6
|
class Point {
int x, y;
};
...
Point p;
|
위와 같이 하였을 때, p의 데이터 멤버 역시 어떤 상황에서는 초기화가 보장되지만 어쩔 때는 안됩니다.
C++의 C부분만을 쓰고 있으며 초기화에 런타임 비용이 소모될 수 있는 상황이라면 값이 초기화된다는 보장이 없습니다.
하지만 STL부분에서는 초기화가 됩니다.
가장 좋은 방법은 모든 객체를 사용하기 전에 항상 초기화하는 것입니다.
기본제공 타입으로 만들어진 비멤버 객체에 대해서는 초기화를 손수 해야 하겠습니다.
1
2
3
4
5
6
|
int x = 0; // int의 직접 초기화
const char * text = "A C-style string"; // 포인터의 직접 초기화
double d; // 입력 스트림에서 읽음으로써 "초기화"
std::cin >> d;
|
이런 부분을 제외하고 나면, C++ 초기화의 나머지 부분은 생성자로 귀결됩니다.
생성자에서 지킬 규칙은 그 객체의 모든 것을 초기화하자! 입니다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class PhoneNumber{...};
class ABEntry{ // ABEntry = "Address Book Entry"
public:
ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones);
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
};
ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones)
{
theName = name; // 지금은 모두 '대입'을 하고 있습니다. '초기화'가 아닙니다.
theAddress = address;
thePhones = phones;
numTimesConsulted = 0;
}
Colored by Color Scripter
|
C++의 규칙에 의하면 어떤 객체이든 그 객체의 데이터 멤버는 생성자의 본문이 실행되기 전에 초기화되어야 한다고 명기되어 있습니다.
ABEntry 생성자를 좀더 멋있게 만들 수 있습니다. 대입문 대신에 멤버 초기화 리스트를 사용하는 방법으로 말이죠!
1
2
3
4
5
6
|
ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones) :
theName(name), // 이제 이들은 모두 초기화되고 있습니다.
theAddress(address),
thePhones(phones),
numTimesConsulted(0)
{ } // 생성자 본문엔 이제 아무것도 들어가 있지 않고요.
|
C++에서의 객체 초기화는 꽤나 변덕스럽지만, 이 와중에도 변덕스럽지 않은 부분이 하나 있습니다.
바로 객체를 구성하는 데이터의 초기화 순서입니다. 이 순서는 어떤 컴파일러를 막론하고 항상 똑같습니다.
1. 기본 클래스는 파생 클래스보다 먼저 초기화된다.
2. 클래스 데이터 멤버는 그들이 선언된 순서대로 초기화된다.
읽는 다른사람들의 혼동도 막고 '무척이나' 찾아내기 힘든 동작 버그도 피하자는 의미에서, 멤버 초기화 리스트에 넣는 멤버들의 순서를 클래스에 선언한 순서와 동일하게 맞춰 주도록 합시다.
이제 걱정거리는 딱 하나가 남았습니다.
비지역 정적 객체의 초기화 순서는 개별 번역 단위에서 정해진다는 사실입니다.
정적 객체란?
1. 전역 객체
2. 네임스페이스 유효범위에서 정의된 객체
3. 클래스 안에서 static으로 선언된 객체
4. 함수 안에서 static으로 선언된 객체
5. 파일 유효범위에서 static으로 정의된 객체
이들 중 함수 안에 있는 정적 객체는 지역 정적 객체라고 하고 나머지는 비지역 정적 객체라고 합니다.
이 다섯 종류의 객체는 프로그램이 끝날 때 자동으로 소멸됩니다. main() 함수의 실행이 끝날 때 정적 객체의 소멸자가 호출된다는 이야기입니다.
번역 단위란?
컴파일을 통해 하나의 목적 파일을 만드는 바탕이 되는 소스 코드를 일컫습니다.
정적 객체의 초기화 순서 때문에 생길 수 있는 문제를 해결하는 방법이 있습니다.
비지역 정적 개체를 하나씩 맡는 함수를 준비하고 이 안에 각 객체를 넣는 것입니다.
함수 속에서도 이들은 정적 객체로 선언하고, 그 함수에서는 이들에 대한 참조자를 반환하게 만듭니다.
사용자 쪽에서는 비지역 정적 객체를 직접 참조하는 과거의 폐단을 버리고 이제는 함수 호출로 대신합니다.
디자인 패턴에 관심이 많은 분이라면 이것이 싱글턴 패턴의 전형적인 구현양식임을 알 수 있겠지요.
꼭 잊지 말아야 할 것!
1. 기본제공 타입의 객체는 직접 손으로 초기화한다. 경우에 따라 저절로 되기도 하고 안되기도 하기 때문이다.
2. 생성자에서는, 데이터 멤버에 대한 대입문을 생성자 본문 내부에 넣는 방법으로 멤버를 초기화하지 말고 멤버 초기화 리스트를 즐겨 사용하자, 초기화 리스트에 데이터 멤버를 나열할 때는 클래스에 각 데이터 멤버가 선언된 순서와 똑같이 나열하자.
3. 여러 번역 단위에 있는 비지역 정적 객체들의 초기화 순서 문제는 피해서 설계해야 한다.
비지역 정적 객체를 지역 정적 객체로 바꾸면 된다.
'언어 > C++' 카테고리의 다른 글
[Effective C++] 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 (0) | 2020.01.06 |
---|---|
[Effective C++] C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자 (0) | 2020.01.05 |
[Effective C++] 낌새만 보이면 const를 들이대 보자! (0) | 2020.01.03 |
[Effective C++] #define을 쓰려거든 const, enum, inline을 떠올리자 (0) | 2020.01.02 |
[Effective C++] C++를 언어들의 연합체로 바라보는 안목은 필수 (0) | 2020.01.01 |