객체를 선언하면 반드시 초기화부터 한다
선언한 객체를 초기화하지 않으면 미정의 동작이 발생할 수 있습니다.
초기화를 안해서 정의되지 않은 동작이 그대로 흘러버려서 어떤 플랫폼의 경우에는
프로그램이 멈춰버릴 수도 있다고 합니다.
대입(assignment) vs 초기화(initialization)
C++에서 초기화란? 클래스 생성자 본문이 실행되기 전에 데이터 멤버는
먼저 초기화되어야한다고 명시되어 있다고 합니다.
class PhoneNumber
{...};
class AddressBook
{
public:
AddressBook(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
{
// 생성자에서 대입 연산을 하고 있는 것이다. (초기화 X)
theName = name;
theAddress = address;
thePhones = phones;
numTimesConsulted = 0;
}
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
};
클래스 생성자 본문에는 데이터 멤버는 초기화가 아닌 대입 연산을 하고 있습니다.
데이터 멤버의 초기화는 멤버 초기화 리스트를 사용해야한다고 합니다.
멤버 이니셜라이저라고 불리기도 하죠.
// AddressBook 생성자
AddressBook(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones) :
theName(name),
theAddress(address),
thePhones(phones),
numTimesConsulted(0)
{
}
초기화 리스트를 사용해서 데이터 멤버를 모두 초기화한 코드입니다.
생성자 본문에서 대입하는 것과 비교했을 때 원하는 값을 주고 시작한다는 점은 똑같지만,
멤버 초기화 리스트를 사용하는 것이 더 효율적입니다.
class PhoneNumber
{...};
class AddressBook
{
public:
AddressBook(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
{
// 생성자에서 대입 연산을 하고 있는 것이다. (초기화 X)
theName = name;
theAddress = address;
thePhones = phones;
numTimesConsulted = 0;
}
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
};
방금전에 봤던 멤버 초기화 리스트를 사용하지 않았던 똑같은 코드입니다.
string 객체로 예를 들자면, 생성자 본문에 보이는 theName = name 이 부분은
이미 string 클래스의 생성자가 호출 되고 나서 대입 연산을 하고 있는 과정입니다.
string 클래스 생성자를 호출하는 의미가 없어졌다고 볼 수 있겠네요.
멤버 초기화 리스트를 사용했다면, string 객체는 복사 생성자를 호출하고 초기화를 종료합니다.
대입 연산자를 연달아서 호출하지 않으므로 보다 효율적인 초기화를 진행할 수 있습니다.
numTimesConsulted = 0;과 같이 기본 데이터 타입의 초기화는
비용의 효율성에서 차이가 없다고 합니다.
그러나 기본 데이터 타입이 상수이거나 참조자로 되어 있을 경우
어차피 대입 연산이 불가능하므로 초기화 리스트를 이용하는 것이 좋습니다.
클래스 데이터 멤버가 많아지고 여러 개의 생성자를 갖고 있을 경우
초기화 리스트를 사용하면 지저분하게 보일 수 있습니다.
이럴 때는 기본 데이터 타입과 같이 대입 연산으로 초기화가 가능한 것을
private 멤버 함수에 몰아 넣고 생성자에서 호출합니다.
외부 파일의 데이터를 읽어와서 초기화할 때 이 방법이 자주 쓰인다고 합니다.
C++ 클래스 멤버 초기화 순서
C++에서 객체의 초기화 순서는 컴파일러와 상관없이 항상 정해져있습니다.
클래스 데이터 멤버는 선언된 순서대로 초기화됩니다.
class PhoneNumber
{...};
class AddressBook
{
public:
// AddressBook 생성자
AddressBook(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones) :
theName(name),
theAddress(address),
thePhones(phones),
numTimesConsulted(0)
{
}
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
};
위와 같은 AddressBook 클래스의 데이터 멤버는
theName(name) → theAddress(address) → thePhones(phones) → numTimesConsulted(0)
이렇게 멤버를 선언한 순서대로 초기화됩니다.
초기화 리스트의 순서가 바뀌어있어도,
초기화 순서는 멤버가 선언된 순서로 진행됩니다.
'Effective C++ 정리' 카테고리의 다른 글
06. 컴파일러가 자동으로 생성하는 함수 호출 없애기 (0) | 2019.12.20 |
---|---|
05. 컴파일러가 자동으로 생성하는 함수 (0) | 2019.12.20 |
03. const 키워드 (0) | 2019.12.14 |
02. 전처리기 보다 컴파일러와 가까이하기 (0) | 2019.12.13 |
01. C++의 구성 요소 (0) | 2019.12.13 |