C++ 규칙에 의하면 어떤객체이든 그 객체의 데이터 멤버는 생성자의 본문이 실행되기 전에 초기화되어야 한다고 명시되어 있습니다.
또한 int, float, double등 기본 제공 타입의 경우에는( 생성자 안에서) 대입되기 전에 초기화되리란 보장이 없습니다. 무엇보다도 기본제공타입의 경우 초기화는 반드시 이루어져야한다.
ex) 초기화가 아닌 생성자 내에서 대입
Test::Test( const std::string& name ) { // 생성자
theName = name; // 초기화가 아니라 대입하고 있습니다.
}
ex) 위의 코드 대신 해결방안
Test::Test( const std::string& name ) : theName(name)
{
// 이처럼 멤버 초기화 리스트를 활용 하면 된다. (멤버 이니셜라이져)
}
초기화 리스트에 들어가는 인자는 바로 데이터 멤버에 대한 생성자의 인자로 쓰입니다.
즉! theName은 name으로 부터 복사생성자에 의해 초기화가 됩니다. 이렇게 되면 기본생성자를 호출하고 곧바로 복사생성자를 호출하는 ( ex)초기화가 아닌 생성자내에서 대입 ) 보다는 효율적입니다.
상수이거나 참조자로 되어있는 데이터 멤버일 경우 반드시 멤버 초기화 리스트로 초기화 되어야 한다.
대입이 불가능하기 때문입니다.
ex) 생성자가 많고, 초기화 리스트에 공통되는 데이터가 많을경우
class Test{
void initialize();
public:
Test();
~Test();
};
Test::Test(){
initialize(); // 초기화 변수가 담긴 private 멤버 함수
}
위의 코드와 같이 별도의 초기화 함수로 옮기는 것도 나쁘지 않습니다. initialize() 함수는 대개 private의 멤버로써 모든생성자에서 이함수를 호출 합니다.
객체를 구성하는 데이터의 초기화 순서
1. 기본 클래스(Base Class)는 파생클래스(Child Class)보다 먼저 초기화 됩니다.
2. 클래스 데이터 멤버는 그들이 선언된 순서대로 초기화 된다.( 따라서 멤버 초기화 리스트도 멤버 선언순서에 맞게 작성하도록 합니다. )
정적객체란?
자신이 생성된 시점에서부터 프로그램이 끝날때까지 살아있는 객체 ( main 함수 종료시 소멸 )
정적객체의 종류
1. 전역객체
2. 네임스페이스 유효범위에서 정의된 객체
3. 클래스 안에서 static으로 선언된 객체
4. 함수 안에서 static으로 선언된 객체
5. 파일 유효범위에서 static으로 정의된 객체
번역단위란?
컴파일을 통해 하나의 목적 파일(object file)을 만드는 바탕이 되는 소스코드, 기본적으로 소스파일 하나가 되는데, 그소스파일안에 #include하는 파일들까지 합쳐서 하나의 번역 단위가 됩니다.
여기서 중요한 사실 하나
만약에 별도로 컴파일된 두개 이상의 소스가 있는데, 각각의 소스파일에는 비지역 정적객체가 한개 이상 들어있을경우 문제가 되는데 이유는 "별개의 번역 단위에서 즉! 각각의 소스파일이 컴파일된 오브젝트에서 정의된 비지역 정적 객체(전역객체)들의 초기화 순서는 정해져 있지 않다." 때문입니다.
난 객체 B에서 객체 A를 쓰고 싶다고 한다면, 당연히 객체 A가 먼저 초기화 되어야 할텐데 초기화 순서가 정해져있지 않기때문에 이 상황은 어떻게 될지 모릅니다.
즉!
이 상황을 해결하기 위해서는 설계를 디자인패턴에 (싱글톤 패턴)으로 구현하여야 합니다.
ex ) 탬플릿 동적 싱글톤 패턴
template <typename T> class Singleton
{
public:
inline static T* getSingletonPtr()
{
static T* ms_Singleton = new T;
return ms_Singleton;
}
}
ex ) 탬플릿 정적 싱글톤 패턴
template <typename T> class Singleton
{
public:
inline static T& getSingletonPtr() // 비정적 객체는 -> 지역 정적객체로 바꿈
{
static T ms_Singleton;
return ms_Singleton;
}
}
위의 템플릿 코드로 구현하게 되면, 지역 정적객체( 함수에서 static 선언한 객체)는 함수 호출중에 그 객체의 정의에 최초로 닿았을때 초기화 되기 때문에, 초기화 순서 문제를 방지 할 수 있게 됩니다.
한가지 문제점이 있습니다.
싱글톨 패턴인 참조자 반환함수는 내부적으로 정적 객체를 쓰기 때문에, 다중스레드 시스템에서는 동작에 장애가 생길 수도 있습니다. 당연합니다.
이와같은 문제의 해결방법으로는 프로그램이 다중스레드로 돌입하기 전의 시동 단계에서 참조자 반환 함수를 전부 손으로 호출해서 초기화 시키는 것 입니다.
이것 만은 잊지 말자!
◆ 기본제공 타입의 객체는 직접 손으로 초기화합니다. 경우에 따라 저절로 되기도 하고 안되기도 하기때문입니다.
◆ 생성자에서는, 데이터 멤버에 대한 대입문을 생성자 본문 내부에 넣는 방법으로 멤버를 초기화하지 말고 멤버 초기화 리스트를 즐겨 사용합시다. 그리고 초기화 리스트에 데이터멤버를 나열할 때는 클래스에 각 데이터 멤버가 선언된 순서와 똑같이 나열합시다.
◆ 여러 번역 단위에 있는 비지역 정적 객체들의 초기화 순서 문제는 피해서 설계해야 합니다. 비지역 정적 객체를 지역 정적 객체로 바꾸면 됩니다.
'0x0001 > Effective C++' 카테고리의 다른 글
[Effective C++] 항목 6 : 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해버리자 (0) | 2019.02.10 |
---|---|
[Effective C++] 항목 5 : C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자 (0) | 2019.02.09 |
[Effective C++] 항목 3 : 낌새만 보이면 const를 들이대 보자! (0) | 2019.02.09 |
[Effective C++] 항목 2 : #define을 쓰려거든 const, enum, inline을 떠올리자 (0) | 2019.02.09 |
[Effective C++] 항목 0 : 시작 하며 (0) | 2019.02.09 |