Effective C++ 공부 내용입니다.


객체 초기화

  • C++에서 언제 객체의 초기화가 보장되며 언제 그렇지 않은지에 규칙이 명확히 정해져있다.

  • 기본적으로 C문법을 사용하는 경우 값이 초기화된다는 보장이 없지만, C++ 문법에서는 상황에 따라 달라진다.

    • 그냥 배열(C)는 확실히 초기화된다는 보장이 없으나, vector(C++)은 보장을 받는 이유가 이러한 법칙 때문이다.


  • 어쩔 수 없이 가장 좋은 방법은 모든 객체를 사용하기 전에 항상 초기화를 해주자.
    • 비멤버 객체에서 기본제공 타입의 경우 초기화를 손수 직접해야 한다.


  • 이런 부분을 지외하고 나면 C++ 초기화의 나머지 부분은 생성자로 귀결된다.
    • 생성자에서 지킬 규칙은 해당 객체의 모든 것을 초기화하는 것만 신경쓰면 된다.


  • 쉬워 보이지만 대입과 초기화를 헷갈리지 않는 것이 중요하다.
class A {};

class B {
public:
	B(const string& name, const string& adress);
private:
	string m_Name;
	string m_Address;
};

B::B(const string& name, const string& adress) {
	m_Name = name;
	m_Address = adress;
}
  • 해당 예시는 초기화가 아니라 대입을 하고 있다.
  • 내가 만든 생성자가 호출되기 전에 디폴트 생성자가 이미 초기화를 하였고, 그 후에 대입이 진행되고 있다.
    • 대부분의 데이터 타입에 대해서는 기본 생성자 호출 후 복사 대입 연산자를 연달아 호출하는 해당 방법보다,
    • 복사 생성자를 한 번 호출하는 쪽이 더 효율적이다.
      • 여기에서 대부분의 데이터 타입에 포함되지 않는 타입은 기본 제공 타입이다.


  • 그렇다면 어떻게 해야 할까?
    • 대입문 대신 member initializer lists를 사용하면 된다.
      • 대입만 사용한 버전의 경우 디폴트 생성자의 초기화를 하고, 곧바로 새로운 값을 대입하고 있는 문제를 해결할 수 있다.
        • member initializer lists에 사용되는 인자가 생성자의 인자로 쓰이기 때문이다.

B::B(const string& name, const string& adress) : m_Name(name), m_Address(adress) {}

  • 사용자가 원하는 값을 주고 시작한다는 점에서 똑같지만, 해당 생성자는 앞선 생성자보다 효율적일 가능성이 크다.



  • 데이터 멤버가 상수이거나 참조자로 되어 있는 경우에는 반드시 초기화를 해야 한다.
    • 왜냐면 상수와 참조자는 대입 자체가 불가능하기 때문이다.


  • 공부를 하다 보니, 어떤 것은 초기화를 해줘야 하고 어떤 것은 괜찮고 다 외우기에는 무리가 있다.
    • 그래서 기본적으로 member initializer lists을 사용을 하는 것이다.


  • 하지만 초기화 리스트가 굉장히 길어져서 보기 힘든 경우가 있다.
    • 이경우 대입을 통하여 초기화가 가능한 것은 초기화 리스트에서 빼내어 하나의 함수에 몰아놓고,
    • 모든 생성자에서 이를 호출하는 것도 나쁘지 않다.
  • 이는 초기값을 파일에서 읽는다든지 데이터베이스에서 찾는 경우에 특히 유용할 수 있다.


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

  • 어떤 컴파일이든 객체의 데이터를 초기화하는 순서는 항상 똑같다.
  1. 기본 클래스는 파생 클래스보다 먼저 초기화
  2. 클래스 데이터 멤버는 그들이 선언된 순서대로 초기화


  • 여기서 중요한 것은 정적 객체이다.
    • 정적 객체에도 종류가 있다고 한다.
  1. 전역 객체
  2. 네임스페이스 유효범위에서 정의된 객체
  3. 클래스 안에서 static으로 선언된 객체
  4. 함수 안에서 static으로 선언된 객체
  5. 파일 유효범위에서 static으로 정의된 객체
  • 4번은 local static object(지역 정적 객체)라고 하고, 나머지는 non-local static object(비지역 정적 객체)라고 한다.


  • translation unit(번역 단위)는 컴파일을 통해 하나의 object file(목적 파일)을 만드는 바탕이 되는 소스 코드를 일컫는다.
    • 여기서 번역은 소스 코드를 기계어로 옮긴다는 의미이다.


  • 여기서 중요한 점은 비지역 정적 객체의 초기화 순서는 개별 번역 단위에서 정해진다.
    • 번역 단위가 다르다면 각각의 번역 단위에서 정의된 비지역 정적 객체들의 초기화 순서가 정해져있지 않다.


  • 별도로 컴파일된 소스 파일이 두 개 이상 있고, 각 소스 파일에 비지역 정적 객체가 한 개 이상 있다고 가정하자.
    • 어떤 번역 단위의 비지역 정적 객체의 초기화가 진행되면서, 다른 쪽 번역 단위에서 해당 비지역 정적 객체를 사용하게 된다면
    • 초기화가 되어있지 않은데 사용하려고 할 수 있다.


  • 해결 방법은 non-local static object를 함수 안에서 정적 객체로 선언하여 해당 객체에 대한 참조자를 반환하는 것이다.
    • 함수 안에서 선언을 다시 했기에 local static object로 판단된다.
      • C++ 규칙 중 하나로 지역 정적 객체는 해당 객체 정의에 최초롤 도달하면 초기화되도록 만들어져있다.
      • 이를 이용하여 반드시 초기화된 정적 객체를 얻는 것이다.


정리

  • 어떤 객체가 초기화되기 전에 그 객체를 사용하는 일이 생기지 않도록 하기 위한 3가지 스킬
    1. 멤버가 아닌 기본제공 타입 객체는 직접 초기화하자.
    2. 객체의 모든 부분에 대한 초기화는 member initializer lists을 사용하자.
    3. 별개의 translation unit에 정의된 non-local static object에 영향을 생각하고 프로그램을 설계하자

댓글남기기