블로그 이미지
Kanais
Researcher & Developer 퍼즐을 완성하려면 퍼즐 조각들을 하나 둘씩 맞춰나가야 한다. 인생의 퍼즐 조각들을 하나 둘씩 맞춰나가다 보면 인생이란 퍼즐도 완성되는 날이 오려나...?

calendar

1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Notice

05-01 21:46

Recent Post

Recent Comment

Recent Trackback

Archive

2015. 4. 16. 11:42 Programming/C/C++

참고 서적 : Effective C++ - Meyers / 곽용재 옮김 / 피어슨에듀케이션코리아

 


1. C++에 왔으면 C++의 법을 따릅시다.

가장 근본적인 것들을 다루고 있는 단원.


항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화 하자

  객체의 값을 초기화하는 데 있어서 C++의 행보는 이랬다저랬다 하는 게 영 마음에 들지 않습니다. 예를 하나 들면, 다음과 같이 했을 때

int x;

어떤 상황에서는 x의 값이 확실히 초기화되지만(0으로), 또 다른 상황에서는 그것이 보장되지 않는다는 것입니다.

아시겠지만, 초기화되지 않은 값을 읽도록 내버려 두면 정의되지 않은 동작이 그대로 흘러 나오게 됩니다. 어떤 플랫폼의 경우에는 미 초기화 객체를 읽기만 해도 프로그램이 서 버리기도 합니다만, 대체적인 경우에는 적당히 무작위 비트의 값을 읽고 객체의 내부가 이상한 값을 갖게 됩니다. 결국엔 프로그램이 신비롭게 동작함은 물론이고 프로그래머는 무한 짜증 디버깅의 시간을 보내야 할 테고요.

C++의 C 부분만을 쓰고 있으며 초기화에 런타임 비용이 소모될 수 있는 상황이라면 값이 초기화된다는 보장이 없습니다. 그렇지만 C가 아닌 부분으로 발을 걸치게 되면 사정이 때때로 달라집니다. 배열(C++의 C 부분)은 각 원소가 확실히 초기화된다는 보장이 없으나 vector(C++의 STL 부분)는 그러한 보장을 갖습니다.

가장 좋은 방법은 모든 객체를 사용하기 전에 항상 초기화하는 것입니다. 기본제공 타입으로 만들어진 비멤버 객체에 대해서는 초기화를 손수 해야 하겠습니다.

int x = 0;                              // int의 직접 초기화

const char* text = "A C-style string";  // 포인터의 직접 초기화

double d;                               // 입력 스트림에서 읽음으로써

std::cin >> d;                          // "초기화" 수행

이런 부분을 제외하고 나면, C++ 초기화의 나머지 부분은 생성자로 귀결됩니다. 생성자에서 지킬 규칙은 지극히 간단합니다. 그 객체의 모든 것을 초기화하자! 입니다.

  참 지키기도 쉬운 규칙입니다만, 대입(assignment)을 초기화(initialization)와 헷갈리지 않는 것이 가장 중요합니다.

class Stu

{

private:

       string name;

       int age;

       string address;

public:

       Stu(string _name, int _age, string _address)

       {

             name = _name;

             age = _age;

             address = _address;

       }

};

여기서 Stu 생성자를 보면 name, age 및 address 는 여기서 초기화되고 있는 것이 아니라, 어떤 값이 대입되고 있는 것입니다.

기본 제공 타입의 객체는 초기화와 대입에 걸리는 비용의 차이가 없지만, 역시 멤버 초기화 리스트에 모두 넣어 주는 쪽이 가장 좋습니다. 또, 데이터 멤버를 기본 생성자로 초기화하고 싶을 때도 멤버 초기화 리스트를 사용하는 습관을 들이세요. 생성자 인자로 아무것도 주지 않으면 되니까 그리 힘든 일도 아닐 것이고요. 예를 들어 매개변수 없는 생성자가 Stu 클래스에 들어 있었다면, 아마 아래처럼 구현할 수 있을 것입니다.

Stu::Stu()

: name(), age(), address() // name, age, address의기본ctor를호출합니다.

{}

어떤 데이터 멤버가 멤버 초기화 리스트에 들어가지 않았고 그 데이터 멤버의 타입이 사용자 정의 타입이면, 컴파일러가 자동으로 그들 멤버에 대해 기본 생성자를 호출하게 되어 있기 때문입니다. 기본 생성자이든 아니든 클래스 데이터 멤버는 모두 초기화 리스트에 항상 올려주는 센스를 정책으로 박아 두셔야만, 어쩌다가 리스트에서 어떤 멤버를 빼먹었을 때 어떤 멤버가 초기화되지 않을 수 있다는 사실을 끌고 가야 하는 부담이 없어지게 됩니다.

 

객체를 구성하는 데이터의 초기화 순서

이 순서는 어떤 컴파일러를 막론하고 항상 똑같습니다.  기본 클래스는 파생클래스보다 먼저 초기화되고,  클래스 데이터 멤버는 그들이 선언된 순서대로 초기화됩니다. 어쩌다가 멤버 초기화 리스트에 넣어진 순서가 다르더라도 초기화 순서는 그래로입니다. 여러분의 코드를 읽는 다른 사람들의 혼동도 막고 ‘무척이나’ 찾아내기 힘든 동작 버그도 피하자는 의미에서, 멤버 초기화 리스트에 넣는 멤버들의 순서도 클래스에 선언한 순서와 동일하게 맞춰 주도록 합니다.

 

비지역 정적 객체의 초기화 순서는 개별 번역 단위에서 정해집니다.

정적 객체(static object)는 자신이 생성된 시점부터 프로그램이 끝날 때까지 살아 있는 객체를 일컫습니다. 그러니까 스택 객체 및 힙 기반 객체는 애초부터 정적 객체가 될 수 없겠지요.

 

정적 객체의 범주에 들어가는 것들

   전역 객체

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

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

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

   그리고 파일 유효범위에서 static으로 정의된 객체

 

이렇게 다섯 종류가 있습니다. 이들 중 함수 안에 있는 정적 객체는 지역 정적 객체(local static object)라고 하고(함수에 대해서 지역성을 가지므로), 나머지는 비지역 정적 객체(non-local static object)라고 합니다. 정적 객체는 프로그램이 끝날 때 자동으로 소멸됩니다. 다시 말해, main() 함수의 실행이 끝날 때 정적 객체의 소멸자가 호출된다는 이야기죠.

번역 단위(translation unit)는 컴파일을 통해 하나의 목적 파일(object file)을 만드는 바탕이 되는 소스 코드를 일컫습니다. 여기서 번역은 소스의 언어를 기계어로 옮긴다는 의미이겠지요. 기본적으로는 소스 파일 하나가 되는데, 그 파일이 #include하는 파일(들)까지 합쳐서 하나의 번역 단위가 됩니다.

 

어떤 객체가 초기화되기 전에 그 객체를 사용하는 일이 생기지 않도록 하려면

딱세가지만 기억해 두고 실천하면 됩니다. 첫째, 멤버가 아닌 기본제공 타입 객체는 여러분 손으로 직접 초기화하세요. 뚤째, 객체의 모든 부분에 대한 초기화에는 멤버 초기화 리스트를 사용합니다. 마지막으로, 별개의 번역 단위에 정의된 비지역 정적 객체에 영향을 끼치는 불확실한 초기화 순서를 염두에 두고 이러한 불확실성을 피해서 프로그램을 설계해야 합니다.

 

이것만은 잊지말자!

l  기본제공 타입의 객체는 직접 손으로 초기화합니다. 경우에 따라 저절로 되기도 하고 안되기도 하기 때문입니다.

l  생성자에서는, 데이터 멤버에 대한 대입문을 생성자 본문 내부에 넣는 방법으로 멤버를 초기화하지 말고 멤버 초기화 리스트를 즐겨 사용합시다. 그리고 초기화 리스트에 데이터 멤버를 나열할 때는 클래스에 각 데이터 멤버가 선언된 순서와 똑같이 나열합시다.

l  여러 번역 단위에 있는 비지역 정적 객체들의 초기화 순서 문제는 피해서 설계해야 합니다. 비지역 정적 객체를 지역 정적 객체로 바꾸면 됩니다.

 

posted by Kanais