[Effective C++] 4. 객체를 사용하기 전에 반드시 그 객체를 초기화하자
C++의 초기화
C++은 객체의 값의 초기화를 보장하지 않는다.
초기화되지 않은 값을 내버려 두면 정의되지 않은 동작이 그대로 나올 수 있기 때문에 항상 초기화를 해주는 것이 좋다.
기본 제공 타입으로 만들어진 비멤버 객체에 대해서 초기화는 아래처럼 할 수 있으며
int x = 0; // int의 직접 초기화
const char * text = "A C-style string"; // 포인터의 직접 초기화
double d;
std::cin>> d; // 입력 스트림에서 읽음으로써 초기화 수행
이런 부분을 제외한 C++의 초기화 나머지 부분은 생성자로 귀결되기 때문에 생성자에서 지킬 규칙은 간단하다.
그 객체의 모든 것을 초기화하면 된다.
이때, 대입(assignment)을 초기화(initialization)와 헷갈리지 않는 것이 가장 중요한데 아래의 클래스를 보자.
class PhoneNumber {...};
class ABEntry
{
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;
}
객체의 데이터 멤버는 생성자의 본문이 실행되기 전에 초기화되어야 하는데, 현재는 어떤 값이 대입되고 있는 것이다.
이를 초기화하려면 아래와 같이 대입문 대신에 멤버 초기화 리스트를 사용하면 된다.
ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones)
: theName(name),
theAddress(address),
thePhones(phones),
numTimesConsulted(0)
{
}
대부분의 데이터 타입에서는 기본 생성자 호출 후에 복사 대입 연산자를 연달아 호출하는 방법(대입) 보다 복사 생성자를 한번 호출하는 쪽(초기화)이 더 효율적이다.
반면 기본 제공 타입은 초기화와 대입에 걸리는 비용의 차이가 없지만 초기화 리스트를 사용하는 게 좋은데, 기본 제공 타입의 멤버도 초기화 리스트 사용이 의무가 될 때가 있기 때문이다. 의무적인 경우란, 상수와 참조자는 대입 자체가 불가능하기 때문에 반드시 초기화되어야 하는 경우를 말한다.
이러한 경우의 수를 다 따져서 초기화하는 것보다 멤버 초기화 리스트를 항상 사용하는 편이 더 쉽고 효율적이기 때문에 어떠한 경우라도 초기화 리스트를 사용하는 것이 좋다.
데이터의 초기화 순서
데이터 초기화 순서는 어떤 컴파일러를 막론하고 똑같으니 알아두어야 한다.
1. 기본 클래스는 파생 클래스보다 먼저 초기화된다.
2. 클래스 데이터 멤버는 그들이 선언된 순서대로 초기화된다.
예외상황
비지역 정적 객체의 초기화 순서는 개별 번역 단위에서 정해진다.
정적 객체(static object)
1. 전역 객체
2. 네임스페이스 유효 범위에서 정의된 객체
3. 클래스 안에서 static으로 선언된 객체
4. 함수 안에서 static으로 선언된 객체
5. 파일 유효 범위에서 static으로 정의된 객체
이들 중 함수 안에 있는 정적 객체는 지역 정적 객체, 나머지는 비지역 정적 객체라고 한다.
번역 단위(translation unit)는 컴파일을 통해 하나의 목적 파일을 만드는 바탕이 되는 소스코드를 일컫는다.
이 말은, 별도로 컴파일된 소스파일이 두 개 이상 있으며 각 소스파일에 비지역 정적 객체가 한 개 이상 들어있고 한쪽 번역 단위에서 다른 쪽 번역 단위의 객체를 사용할 경우 이 객체가 초기화되어 있지 않을 수도 있다는 얘기다.
즉, 서로 다른 번역 단위에 정의된 비지역 정적 객체들 사이의 상대적인 초기화 순서는 정해져 있지 않으므로 이 점을 조심해서 설계해야 하며, 이를 피하기 위해서는 비지역 정적 객체를 지역 정적 객체로 바꾸면 된다.
class FileSystem {...};
FileSystem& tfs() // 비지역 정적 객체를 함수로 대신
{
static FileSystem fs; // 지역 정적 객체를 정의하고 초기화
return fs; // 이 객체에 대한 참조자를 반환
}
class Directory {...};
Directory::Directory(params)
{
std::size_t disks = tfs().numDisks();
}
Directory& tempDir() // 비지역 정적 객체를 함수로 대신
{
static Directory td; // 지역 정적 객체를 정의/초기화
return td; // 이 객체에 대한 참조자를 반환
}
요약
- 기본 제공 타입의 객체는 직접 손으로 초기화하자. 경우에 따라 저절로 되기도 하고 안되기도 하기 때문이다.
- 생성자에서는 멤버 초기화 리스트를 사용해서 초기화하자. 그리고 초기화 리스트에 데이터 멤버를 나열할 때는 클래스에 각 데이터 멤버가 선언된 순서와 똑같이 나열하자.
- 여러 번역 단위에 있는 비지역 정적 객체들의 초기화 순서 문제는 피해서 설계해야 한다. 비지역 정적 객체를 지역 정적 객체로 바꾸자.
'Books > Effective C++' 카테고리의 다른 글
[Effective C++] 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 (0) | 2022.05.09 |
---|---|
[Effective C++] 5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자 (0) | 2022.05.08 |
[Effective C++] 3. 낌새만 보이면 const를 들이대 보자! (0) | 2022.05.07 |
[Effective C++] 2. #define을 쓰려거든 const, enum, inline을 떠올리자 (0) | 2022.05.06 |
[Effective C++] 1. C++을 언어들의 연합체로 바라보는 안목은 필수 (0) | 2022.05.04 |
댓글
이 글 공유하기
다른 글
-
[Effective C++] 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자
[Effective C++] 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자
2022.05.09 -
[Effective C++] 5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자
[Effective C++] 5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자
2022.05.08 -
[Effective C++] 3. 낌새만 보이면 const를 들이대 보자!
[Effective C++] 3. 낌새만 보이면 const를 들이대 보자!
2022.05.07 -
[Effective C++] 2. #define을 쓰려거든 const, enum, inline을 떠올리자
[Effective C++] 2. #define을 쓰려거든 const, enum, inline을 떠올리자
2022.05.06