1. 초기화 규칙
C++ 객체 초기화에 대한 규칙은 명확히 준비되어 있으나 복잡하여 그것을 외우기에 가치가 떨어진다.
따라서, 가장 추천하는 방법은 모든 객체를 사용하기 전에 항상 초기화하는 것이다.
2. 초기화 vs 대입
대입의 예
ABEntry::ABEntry(const std::string& name, const std::string& addess,
const std::list<PhoneNumber>& phones)
{
theNmae = name;
theAddress = address;
thePhones = phones;
numTimesConsulted =0;
}
대입의 문제점
기본 제공 타입 변수 (여기서 numTimesCounsulted)는 미리 초기화된다는 보장이 없다.
나머지 string 변수들은 초기화가 일어나기 전에 기본 생성자가 호출된다.
해결방법?
대입문 대신에 초기화 리스트를 사용하자!
ABEntry::ABEntry(const std::string& name, const std::string& addess,
const std::list<PhoneNumber>& phones):theName(name),
theAddress(address),thePhones(phones),numTimesConsulted(0)
{}
// 기본 생성자도 초기화 리스트를 이용하자!
ABEntry::ABEntry(const std::string& name, const std::string& addess,
const std::list<PhoneNumber>& phones):theName(),
theAddress(),thePhones(),numTimesConsulted(0)
{}
대입 | 초기화 리스트 |
기본 생성자 호출/ 복사 대입 연산자 호출 | 복사 생성자 호출 |
클래스 멤버 데이터는 모두 초기화 리스트를 통해 초기화 하자. 디버깅에도 유리하다.
기본제공 타입의 멤버를 반드시 초기화 리스트로 초기화해야 하는 경우
상수, 참조자로 되어 있는 데이터 멤버인 경우 대입 자체가 불가능하기 때문에 초기화 리스트로 '초기화' 해주어야 한다.
3. 초기화 리스트를 대신하는 방법
하나의 클래스에 여러 개의 생성자가 있는 경우
대입으로 초기화가 가능한 데이터 멤버들을 초기화 리스트에서 빼내어 별도의 함수로 옮기는 것도 좋다!
4. 객체를 구성하는 데이터의 초기화 순서
(1) 기본 클래스 > 파생 클래스
(2) 클래스 데이터 멤버는 선언된 순서대로 초기화!
class test{
private:
int a;
int b;
int c;
}
a->b->c 순서로 초기화된다. 초기화 리스트에 순서가 다르게 넣어진데도 초기화 순서는 a->b->c 순서이다.
여기까지가 기본 제공 타입의 비멤버 객체, 기본 클래스, 데이터 멤버 초기화 방법이다.
5. 비지역 정적 객체의 초기화 순서는 개별 번역 단위에서 정해진다
-> 무슨 소리야 ㅠㅠ..
(1) 정적 객체
자신이 생성된 시점부터 프로그램이 끝날 때까지 살아 있는 객체
- 전역 객체
- 네임스페이스 유효 범위에서 정의된 객체
- 클래스 안에서 static으로 선언된 객체
- 함수 안에서 static으로 선언된 객체
- 파일 유효범위에서 static으로 정의된 객체
(2) 지역 vs 비지역
함수 안에 있는 정적 객체는 지역 정적 객체, 나머지는 비지역 정적 객체
(3) 번역 단위
컴파일을 통해 하나의 목적 파일을 만드는 바탕이 되는 소스 코드 (#include 하는 파일들까지 합친 것을 의미)
다시 말해, 둘 이상의 컴파일된 소스 파일에 있어서 한쪽 번역 단위에 있는 비정적 객체의 초기화가 진행되면서 다른 쪽 번역 단위에 있는 비지역 정적 객체가 사용될 때 비지역 정적 객체가 초기화되어 있지 않을 수 있다.
-> non static class 내에서 class 외부에 static으로 정의된 변수로 초기화하려는 경우를 말하는 것 같다.
-> 결국 서로 다른 번역 단위에 정의된 비지역 정적 객체들 사이의 상대적인 초기화 순서는 정해져 있지 않다.
class FileSystem{
public:
// ...
std::size_t numDisks() const;
// ...
};
extern FileSystem tfs;
class Directory{
public:
Directory(params);
// ...
};
Directory::Directory(params)
{
// ...
std::size_t disks = tfs.numDisks();
}
// if 어디선가 선언
Directory tempDir (params);
tfs가 tempDir보다 먼저 초기화되지 않는다면 tempDir 생성자는 초기화되지 않은 tfs를 사용하는 꼴
6. Singleton Pattern을 통한 해결
위의 문제를 해결하기 위해서, 비지역 정적 객체를 하나씩 맡는 함수를 준비하고 이 안에 각 객체를 넣는다. 함수 속에서도 이들은 정적 객체로 선언하고, 그 함수에서는 이들에 대한 참조자를 반환하게 만든다. 정리하자면, 비지역 정적 객체가 지역 정적 객체로 바뀐 것이다.
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;
}
비지역 정적 객체를 사용하는 대신 해당 객체를 반환하는 함수를 이용한다.
7. Singleton Pattern의 문제점
다중 스레드 환경에서 비 상수 정적 객체는 시한폭탄과 같다.
이것만은 잊지 말자!
- 기본 제공 타입의 객체는 직접 손으로 초기화합니다. 경우에 따라 저절로 되기도 하고 안되기도 하기 때문입니다.
- 생성자에서는, 데이터 멤버에 대한 대입문을 생성자 본문 내부에 넣는 방법으로 멤버를 초기화하지 말고 멤버 초기화 리스트를 즐겨 사용합시다. 그리고 초기화 리스트에 데이터 멤버를 나열할 때는 클래스에 각 데이터 멤버가 선언된 순서와 똑같이 나열합시다.
- 여러 번역 단위에 있는 비지역 정적 객체들의 초기화 순서 문제는 피해서 설계해야 합니다. 비지역 정적 객체를 지역 정적 객체로 바꾸면 됩니다.
'C++ > Effective C++' 카테고리의 다른 글
[Effectivce C++] 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 (0) | 2020.07.27 |
---|---|
[Effectivce C++] 5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자 (0) | 2020.07.27 |
[Effective C++] 3. 낌새만 보이면 const를 들이대 보자! (0) | 2020.07.17 |
[Effective C++] 2. #define을 쓰려거든 const,enum,ilnine을 떠올리자 (0) | 2020.07.13 |
[Effective C++] 1. C++를 언어들의 연합체로 바라보는 안목은 필수 (0) | 2020.07.13 |