관리 메뉴

기억을 위한 기록들

[CPP-effective] 1-3 객체를 사용하기전에는 초기화를하자. 본문

C & CPP/Effective C++

[CPP-effective] 1-3 객체를 사용하기전에는 초기화를하자.

에드윈H 2021. 3. 1. 19:45

초기화되지 않은 값을 읽도록 내버려 두면 정의되지 않은 동작이 그대로 흘러나오게 된다.

어떤 플랫폼의 경우 미초기화 객체를 읽기만 해도 프로그램이 서버리기도 한다.

 

기본 제공 타입으로 만들어진 비멤버 객체에 대해서 직접 초기화 

int x = 0; //int의 직접 초기화
const char* text="Hello"; //포인터의 직접 초기화

double num;
std::cin>>num; //입력스트림에서 읽음으로써 "초기화" 수행

이런 부분을 제외하면 나머지는 생성자로 귀결된다.

여기서 대입(assignment)과 초기화(initialization)를 헷갈리면 안 된다.

 

아래와 같이 해주고 있는 건 대입이다.

class A
{
public:
    A();
private;
    int a;
    int b;
}


A::A()
{
    a=0;
    b=0;
}

그리고 아래의 방법이 초기화이다. (멤버 초기화 리스트)

class A
{
public:
    A();
private;
    int a;
    int b;
}


A::A()
   : a(0)
   , b(0)
{
}

 

대입의 방법이 틀린 것은 아니지만, 초기화가 더 효율적일 수 있고 멤버 변수가 예를 들어 클래스 멤버 변수가 상수 거나 참조자라면 대입 자체가 불가능하다. 그래서 초기화로 해주어야 한다.

 

걸리는 점이라면 클래스가 여러 개 수가 되어 멤버 초기화 리스트를 주렁주렁 달고 있다면 그렇게 좋아 보이진 않을 것이다.

그나마 방법을 쓰자면, 대입으로도 초기화 가능한 멤버 변수 데이터들은 따로 빼내어 별도의 함수로 옮기는 것도 나쁘진 않다. (그래도 멤버 초기화 리스트가 좋긴 하다.)

 

 

클래스 생성자에 관해 추가로 알아둬야 할 점은

1. 자식 클래스보다 부모 클래스가 먼저 초기화가 된다.

2. 클래스 데이터 멤버 변수들도 선언된 순서대로 초기화된다(멤버 초기화 리스트에서 순서가 바뀌어도) 그래서 클래스 내의 선언 순서대로 해주는 편이 낫긴 하다.

 

라는 점이다.

 

 

추가로 비지역 정적 객체의 초기화 순서는 개별 번역 단위에서 정해진다라는 점이다.

번역 단위?
컴파일을 통해 하나의 목적 파일을 만드는 바탕이 되는 소스를 일컫는다.(번역 : 소스의 언어를 기계어로 옮긴다는 뜻) 기본적으로 소스파일이 하나가 되는데, #include하는 파일까지 합쳐서 하나의 번역단위가 된다.

정적 객체는 생성된 시점부터 프로그램이 끝날 때까지 살아 있는 객체를 일컫는다. 범주안에는

1. 전역 객체

2. 네임스페이스 유효 범위에서 정의된 객체

3. 클래스 안에서 static으로 선언된 객체

4. 함수 안에서 static으로 선언된 객체

5. 파일 유효 범위 안에서의 static객체 이렇게 5가지이다.

 

이중 정적 객체는 4번 함수 안에서 static으로 선언된 객체지역 정적 객체(local static object)라고 하고, 나머지는 비지역 정적 객체(non-local static object)라고 한다.

 

위 5종류 합쳐서 정적 객체는 프로그램이 끝날 때 자동으로 소멸된다. main() 함수의 실행이 끝날 때 정적 객체의 소멸자가 호출된다는 것이다.

 

 

문제는 이러하다.

class A
{
public:
    A();
    A GetA()
    static A ref;
private:
    int a;
    int b;
   
}



//다른 번역 단위
//..
A a;
a.ref; //ref변수 사용

별도로 컴파일 된 소스 파일 2개 이상이 있으며, 각 소스파일에 비지역 정적객체가 1개 이상 들어 있는 경우 어떻게 되느냐라는 것이다.

한쪽 번역 단위에 있는 비정적 객체의 초기화가 진행되면서, 다른 쪽 번역 단위에 있는 비지역 정적객체가 사용되는데, 불행히도 이 객체가 초기화 되어 있지 않을지도 모른다는 점이다.

(별개의 번역 단위에서 정의된 정적 객체들의 초기화 순서는 '정해져 있지 않다'라는 점때문이다.)

해결 방법으로는 비지역 정적 객체가 지역 정적 객체로 바뀌게 하면 된다. 바로 싱글톤패턴의 구현 방식과 같다.

 

class A
{
public:
    A();
    A GetA()
    { 
         static A ref;
         return ref;
    }
private;
    int a;
    int b;
}

 

 

 

정리

* 기본 제공 타입의 객체는 직접 손으로 초기화한다.(어쩔땐 자동으로 되긴하는데, 어쩔때는 안된다.

 

* 생성자에서는 데이터 멤버에 대한 대입문을 생성자 본문 내부에서 넣지말고 멤버 초기화리스트를 많이 사용하자.

그리고 초기화 리스트에서 데이터를 나열할때엔 클래스에서 선언한 순서와 똑같이 나열하자.

 

* 여러 번역 단위에 있는 비지역 정적 객체들의 초기화 순서문제는 피해서 설계해야한다.

비지역 정적 객체를 지역 정적객체로 바꾸면 된다.