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

수민이슈·2023년 3월 14일
0

Effective C++

목록 보기
4/30
post-thumbnail

스콧 마이어스의 Effective C++을 읽고 개인 공부 목적으로 요약 작성한 글입니다!

💡 기본제공 타입의 객체는 직접 손으로 초기화한다! (경우에 따라 자동 초기화 유무 바뀜)
💡 생성자에서는 멤버 초기화 리스트를 사용해서 초기화하자!
(초기화 리스트에 데이터 멤버를 나열할 때는 각 데이터 멤버의 선언 순서와 동일하게!!)
💡 여러 번역 단위에 있는 비지역 정적 객체들의 초기화 순서 문제는 피해서 설계하자
-> 비지역 정적 객체를 지역 정적 객체로 변경!


🖊️ 초기화를 해야지

초기화하지 않은 값을 읽어버리면 정의되지 않은 값이 그대로 나옴
초기화가 될거란 보장이 없다 ㅠㅠ
제발 모든 객체를 사용하기 전에 항상 초기화하자!


🖊️ 생성자에서의 초기화

Bad : 이건 대입이지 초기화가 아니야

class PhoneNumber { ... };

class ABEntry {
public:
	ABEntry(const string& name, const string& address, const list<PhoneNumber>& phones);
    
private:
	string theName;
    string theAddress;
    list<PhoneNumber> thePhones;
    int numTimesConsulted;
};

ABEntry::ABEnrty(const string& name, const string& address, const list<PhoneNumber>& phones)
{
	theName = name;
    theAddress = address;
    thePhones = phones;
    numTimesConsulted = 0;
}

마 장난하나
이건 대입이지

왜 이게 잘못됐냐면

어떤 객체이든 객체의 데이터 멤버는 생성자 본문이 실행되기 전에 초기화되어야 함
그니까 생성자 본문에서 theName, theAddress, thePhones, numTimesConsulted는 이미 디폴트 생성자를 호출해서 초기화되어있고, 생성자 본문에서 걍 새로운 값을 대입하는 거..
(디폴트 생성자 + 복사 대입 연산자 호출)

그치만 가끔 쓸 때도 있음

대입 연산을 하나의 private 함수에 몰아놓고 모든 생성자에서 이 함수를 호출하는 방식으로,,
파일이나 DB에서 진짜 초기값을 읽어올 때 이렇게 사용하기도 한당

Good : 멤버 초기화 리스트 사용

ABEntry::ABEnrty(const string& name, const string& address, const list<PhoneNumber>& phones)
	: theName(name),
    theAddress(address),
    thePhones(),				// 디폴트 생성자로 초기화할때도 초기화는 하셈
    numTimesConsulted(0)
{}

초기화 리스트에 들어가는 인자는 데이터 멤버에 대한 생성자의 인자로 쓰인다!
(복사 생성자 호출)


🖊️ 초기화 순서

불변의 초기화 순서 규칙

  1. 기본 클래스는 파생 클래스보다 먼저 초기화된다
  2. 클래스 데이터 멤버는 그들이 선언된 순서대로 초기화된다.

🖊️ 별개의 번역단위에서 정의된 비지역 정적 객체의 초기화 순서는 정해져 있지 않다!

정적 객체 (Static Object)

: 자신이 생성된 시점부터 프로그램이 끝날 때까지 살아있는 객체

  • 전역 객체
  • namespace 유효범위에서 선언된 객체
  • 클래스, 함수 내부, 파일 유효 범위에서 static 선언된 객체

지역 정적 객체 : 함수 안에 있는 정적 객체
비지역 정적 객체 : 나머지

번역 단위

: 컴파일을 통해 하나의 목적 파일을 만드는 바탕이 되는 소스코드
기본적으로 소스 파일 하나..
#include하는 파일들까지 합쳐서 하나의 번역 단위

그래서 뭐가 문젠데?

  1. 별도로 컴파일된 소스파일이 2개 이상 있는데
  2. 각 소스파일에 비지역 정적 객체 (전역, namespace, class, file)가 하나 이상 있는 경우에
  3. 한쪽 번역단위에 있는 비정적 객체의 초기화가 진행되면서 다른 번역단위에 있는 비지역 정적 객체가 사용되는 경우

이 비지역 정적 객체가 초기화되어 있지 않을 수도 있다 ㅠㅠ!

class FileSystem {
public:
	...
    std::size_t numDisks() const;
    ...
};

extern FileSystem tfs; 



class Directory{
public:
	Directory(params);
    ...
};

Directory::Directory(params)
{
	...
    std::size_t disks = tfs.numDisks();			// 여기서 문제
    ...
}

Directory tempDir (params);

이 경우에, tfs가 tempDIr보다 먼저 초기화되지 않으면
tempDir의 생성자는 tfs가 초기화되 않았는데 tfs를 사용하려고 함

-> 서로 다른 번역 단위 안에서 정의된 비지역 정적 객체들이기 때문에 이런 문제가 발생한다

해결 방법 : 싱글톤 패턴 방식

비지역 정적 객체 -> 지역 정적 객체로 변경하자!

비지역 정적 객체를 하나씩 맡는 함수를 준비하고 이 안에 각 객체를 넣는다. 함수 안에서 정적으로 선언하고 참조자를 반환하도록 한다.

비지역 정적 객체를 직접 호출하지 않고 함수 호출(반드시 초기화된 객체를 참조)로 대신한다!

익숙하쥬?

class FileSystem { ... };
FileSystem& tfs() {					// tfs 객체 대산 tfs() 함수로 대체!
	static FileSystem fs;			// 함수가 클래스 안에 static 멤버로 들어가도 된다
    return fs;
}

class Directory { ... };
Directory::Directory(params) {
	...
    std::size_t disks = tfs().numDisks();
    ...
}

Directory& tempDir() {
	static Directory td;
    return td;
}

하지만 멀티쓰레드에서 싱글톤은 정말 끔찍하다
알자너 ㅎ..


😊느낀점

초기화 할 때는 멤버 초기화 리스트를 써서 생성자 본문이 호출되기 전에 하자.
그리고 싹 다 초기화하자 진짜 죽기 싫으면...

0개의 댓글