c++의 객체(변수)는 자동으로 초기화가 중구난방적으로 하는 것은 아니다 하지만 초기화가 일어나는 조건을 다 알려면 힘들고 실수도 할 수 있기 때문에 일단은 다 초기화 하자
int x = 0; //int의 직접 초기화
const char* text = "A C-style string"; //포인터의 직접 초기화
//(항목 3도 참조)
double d; //입력 스트림에서 읽음으로써
std::cin >> d; //"초기화" 수행
그리고 초기화와 대입을 헷갈리지 말자 일단은 대입코드의 예시를 보겠다
class PhoneNumber { .... };
class ABEntry // ABEntry = "Address Book Entry"
{
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber>thePhones;
int numTimesConsulted;
public:
ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones);
};
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
{
theName = name;
theAddress = address; //지금은 모두 '대입'을 하고 있다
thePhones = phones; //'초기화'가 아니다
numTimesConsulted = 0;
}
이 코드에 경우에는 대입을 하고 있다. 이 과정은 2과정이 된다.
기본 생성자에서 초기화를 매개변수가 있는 생성자에서 대입이 된다.
기본 생성자에서 한 초기화는 쓸모가 없어진 셈이다. 그러면 두 번째 예시 코드를 보자
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
: theName(name),
theAddress(address), //이제 이들은 모두 초기화되고 있다
thePhones(phones),
numTimesConsulted(0)
{} //생성자 본문엔 이제 아무것도 들어가 있지 않는다
이 코드에서는 멤버 초기화리스트로 초기화를 한다면 복사 생성자 하나로 초기화가 된다. 그러므로 생성자의 초기화 리스트에서 객체들은 초기화하는 습관을 들이자, 물론 순서도 선언된 순서대로 해야 한다 이유는 원래 선언된 순서대로 초기화를 시켜주니까 그것에 지켜주도록 하자 ^^
이제 정적객체에 대해서 알아보자
정적객체(static object)는 생성된 시점부터 프로그램이 끝날 때까지 살아있는 객체를 말한다. 종류는
1.전역객체
2. 네임 스페이스 유효 범위에 정의된 객체
3. 클래스 안에 static으로 선언된 객체
4. 함수 안에서 static으로 선언된 객체
5. 파일 유효 범위에서 static으로 정의된 객체
이렇게 5개가 있고 4번인 함수 안에서 선언된 정적객체는 지역 정적객체라 하며 그 나머지는 비지역 정적 객체라 한다.
그리고 또 번역 단위라는 것이 있다. 컴파일을 통해 하나의 목적 파일(object file)을 만드는 소스코드를 말한다. 기본적으로 #include까지 합쳐서 하나의 번역 단위가 된다.
이제 문제에 대해서 알아보자 정적 객체는 딱히 초기화가 되는 순서가 없다 그리고 또 다른 별개의 번역 단위에서 다른 번역단위의 정적객체를 쓸 경우 문제가 생길 수 있다. 이유는 별개의 번역단위에 있는 것들은 서로 비정적객체가 초기화되었는지 모른다는 것이다. 아래의 코드를 보자.
class FileSystem //여러분의 라이브러리에 포함된 클래스
{
public:
.....;
std::size_t numDisks() const; //많고 많은 멤버 함수들 중 하나
.....;
};
extern FileSystem tfs; //사용자가 쓰게 될 객체
//"tfs" = "the file system"
위의 코드는 파일 시스템 코드이다. 코드가 크면 그만큼 초기화가 느려질 것이다. 이번에는 아래의 코드를 봐보자
Directory::Directory(params)
{
....;
std::size_t disks = tfs.numDisks(); //tfs 객체를 여기서 사용함
....;
}
이 코드에서 파일 시스템의 정적 객체를 사용하는 것을 볼 수 있다.
더 나아가 임시 파일을 만든답시고
Directory tempDir(params);
를 호출해버렸는 파일 시스템의 초기화되지 않은 정적객체를 사용하면 대참사가 일어날 것이다. 이럴 때 방법이 있다.
class FileSystem //여러분의 라이브러리에 포함된 클래스
{
public:
.....;
std::size_t numDisks() const; //많고 많은 멤버 함수들 중 하나
.....;
};
FileSystem& tfs() //tfs 객체를 이 함수로 대신한다. 이 함수는
//클래스 안에 정적 멤버로 들어가도 된다.
{
static FileSystem fs; //지역 정적 객체를 정의하고 초기화 한다.
return fs; //이 객체에 대한 참조자를 반환한다.
}
class Directory
{
public:
Directory{ param };
...
};
Directory& tempDir() //tempDir 객체를 이 함수로 대신한다. 이 함수는
//Directory 클래스의 정적 멤버로 들어가도 된다.
{
static Directory td; // 지역 정적 객체를 정의/초기화한다.
return td; //이 객체에 대한 참조자를 반환한다.
}
위에 코드처럼 함수로 참조를 하면 된다.
그리고 또 한 가지 문제가 있다. 다중 스레드이다. 다중 스레드는 비상수정적객체(비정적,정적)가 시한폭탄 같은 거다. 그런 골칫거리를 해결하는 방법으로 다중 스레드로 돌입하기 전 시동 단계에서 참조자 반환 함수를 손으로 다 호출하는 것이다.(이 부분 이해 못함 알아봐야겠엉 ㅠㅠ)
이제는 끝났다. 물론 위의 방법은 객체들의 초기화 순서가 잘 맞아야 할 수 있다.
B초기화 전에 A가 초기화돼야 하는데 A가 초기화되려면 B에 의존해야 하는 이런 거 말이다
이것만은 잊지 말자
1. 기본 제공 타입의 객체는 직접 손으로 초기화한다. 경우에 따라 저절로 되기도 하고 안되기도 하고 안되기도 하기 때문이다.
2. 생성자에서는, 데이터 멤버에 대한 대입문을 생성자 본문 내부에 넣는 방법으로 멤버를 초기화하지 말고 멤버 초기화 리스트를 즐겨 사용하자. 그리고 초기화 리스트에 데이터 멤버를 나열할 때는 클래스에 각 데이터 멤버가 선언된 순서와 똑같이 나열한다.
3. 여러 번역 단위에 있는 비지역 정적 객체들의 초기화 순서 문제는 피해서 설계해야 한다. 비지역 정적 객체를 지역 정적 객체로 바꾸면 된다.