초기화 리스트

CJB_ny·2022년 8월 11일
0

C++ 정리

목록 보기
51/95
post-thumbnail

초기화 리스트

초기화를 왜 하는가?

귀찮은데...

이상태로 실행하면 컴파일 에러가 난다

=> 초기화 되지 않은 지역변수 사용.

그런데 이것은 컴파일 레벨을 조정하기에 따라서 달라진다.

(기본 설정은 에러)

이후 기본생성자만 추가를 해서 객체를 만들면

0이 들어가야할거 같지만 -87828485? 이라는 이상한 값 들어가게 된다.

(C#에서는 값형식에 대해서 초기화를 무조건 해야한다 그런데 int a; => 출력하면 0 나옴)

스택 메모리

밀물 썰물처럼 굉장히 불안정한 메모리 이다.

이전 녀석이 사용했던 메모리 주소를 내가 다시 사용한다거나 그럼.

계속 공유함.

main안에서 Knight k; 선언하면 => 스택에 올라가는데

메모리를 까보면

이렇게 cccccccc라는 값이 들어가있다.

즉, 이전 사람이 "사용하던 값"이 그대로 남아있는 것이다.

딱 드래그 한 부분을

Knight k의 _hp의 값으로 사용할 주소인데

이전 입주민이 정리를 하나도 안해놓은 상태인 것이다.

그래서 -8696923 어쩌구가

ccccccc라는 값을 10진수로 변환을 하였을 때 나오던 값이다 ❗

그래서 이거 쓰레기값 들어가게 된다.

그래서 초기화 왜 하냐?

    1. 버그 예방 중요
    1. 포인터 등 주소값이 들어가 있을 경우 더욱더 주의

초기화 방법 ❗

  1. 생성자 내에서

  2. 초기화 리스트 (오늘 배울 꺼)

  3. C++ 11 문법

C++ 11, 생성자 내에서

public 안에서

int m_iHp = 100; => C#이나 다른 언어에서는 자연스러운 건데

이게 C++11 문법이란다. C++2011 에서 추가된 새로운 문법이다.

상속시 부모 생성자 호출

자식 생성자에서 Knight : Player() 부모 생성자를 명시적으로 집어 줄 수 있는데

인자를 넣어주면 인자를 받는 부모 생성자를 호출하고 아무것도

안하면 부모의 기본 생성자를 호출한다.라고 공부함.

초기화 리스트

지금 이부분이 초기화 리스트 부분이다.

그런데

  1. 생성자 내에서 초기화 방법이랑

  2. 초기화 리스트 안에서의 초기화 방법이랑

미묘한 차이점 존재.

생성자 안에서 초기화 vs 초기화 리스트 차이점

class Knight
{
public:
	Knight()
    {
    	_hp = 100;
    }
    
}

class Knight2
{
public:
	Knight()
    :
    _hp = 100;
    {
    	
    }
    
}
  • 일반 변수는 별 차이가 없음.

  • 멤버 타입이 클래스인 경우 차이가 난다.

클래스 추가시 고려 사항 ❗❗

Is-A && Has-A ❗❗❗❗❗❗❗

이런 인벤토리 클래스를 만들어 Knight나 Archer와 같은 클래스에 주고싶을 경우 (상속)

고려해야 할 사항이

Knight가 어떤식으로 Inventory를 들고있어야 할지???

이것을 설계를 할 때는 두가지 법칙중 하나를 선택.

  1. Is-A : Knight Is-A Player?

  2. Has-A : Knight Has-A Inventory?

인지 고려 해야함.

설계를 하기 전에 이 두가지 질문을 던지고 답을 해야함.

Knight Is-A Player? => 이 질문은 자연스럽다. => OK 상속관계

Knight Is-A Inventory? => 그냥 딱봐도 어색하다. => NO

Knight Has-A Inventory? => 기사는 인벤토리를 가지고있다. => OK 포함관계

Has-A가 맞다면

Knight의 멤버 변수로

이렇게 들고 있도록 해주자.

클래스를 멤버 변수로 들고있는 경우

int _hp와 같은 변수들을 멤버 변수로 들고있을 경우에는 상관이 없는데

클래스를 멤버 변수로 들고있을 경우

Knight객체가 만들어짐과 동시에

이 _inventory가 간접적으로 선처리 영역(초기화 리스트)부분에서

같이 만들어지게 된다.

class Knight : public Player
{
public:
	Knight()
    :
        // 선처리 영역
    {
    	_hp = 100;
    }
public:
	int _hp;
    Inventory _inventory;
};

지금 Knight의 기본생성자를 호출시 부모의 기본생성자가 default로 호출이 되는 것처럼

(Knight : Player(1)로 명시해주면 다른 부모 생성자 호출 가능함)

마찬가지로 Knight가 Inventory를 들고있는 관계 (포함관계)에서는

Knight라는 것을 만들기 위해서는 Inventory라는 녀석을 딸려서 같이 만들어 줘야하기 때문에

Knight 의 선처리 영역에서 Inventory의 "기본 생성자"가 같이 호출이 되게 된다.

초기화 리스트와 포함관계 에서의 생성자 호출의 연관성??

이게 현재 Inventory클래스이다.

그런데 Knight에서 인벤토리 기본생성자가 아니라 인자를 int size를 받는 생성자를 호출 하고싶다고 가정을 하자.

문제점

그래서 Knight의 생성자내에서 Inventory클래스를 초기화 해줄 경우

인벤토리 클래스 생성자가 두번 호출이 되는 상황이 발생한다.

즉 선처리 영역에서 처리를 해주지 않아서 일어난 문제임.

Knight생성자가 호출이 되면은

생성자의 선처리 영역이 먼저 처리가 된 다음에

그 다음에

이부분이 호출이 되는데

사실상 이런 코드가 간접적으로 들어가 있는 것이다.

그런데 초기화 리스트 다음에 {} 부분에서

"초기화 리스트 부분이 아니라 _inventory = Invectory(20);

으로 다시 만들어 줘!" 라고 요청 한 것임.

그러면 기존의 _inventory가 덮어 쓰게 되니까

초기화 리스트에서 만들었던

_inventory = Inventory() 녀석은 필요없게 되니까

선처리 영역에서 만들었던 애는 필요가 없다! 라는 코드를

간접적으로 날려주게 되어 (코드가 같이 들어가게 되어서)

클래스를 멤버 변수로 -> 생성자 호출 순서

이 생성자가 먼저 호출이 된 다음에

이어서

이 소멸자가 호출이 되고

그다음에

우리가 덮어쓴 이 생성자가 호출이 된거임.

그래서

  1. 선처리 영역에서 인벤토리 기본 생성자를 호출한다.

  2. 인벤토리 클래스 인자를 받는 생성자를 호출한다.

  3. 기본생성자로 만든 _inventory는 필요없어서 소멸자 호출한다.

  4. 그다음 inventory 생성자 코드 흐름 진행한다.

그래서 결론

이런 Has-A 포함관계가 있을 경우에도

Knight생성자 {} 이 부분도 "생성자 외부"이기 때문에

이런 외부에서 처리를 할게 아니라

선처리 영역에서

이렇게 한방에 같이 처리를 해야한다.

선처리 영역에서 할 수 밖에 없는 경우 ❗

  1. 정의함과 동시에 초기화가 필요한 경우

    참조 타입, const 타입 같은 경우

이렇게 멤버 변수로 참조 변수랑, const변수 들고있다고 해보자.

(참조는 뭔가를 참조를 해야함 => 참조하지 않으면 에러)

참조타입은 포인터와 다르다.

포인터는 0(nullptr)을 가르킬 수 있지만

참조는 바구니의 또 다른 이름이라 무조건 어떤 애를 가르켜야한다.

그래서 이 참조변수를

생성자 내부에서 이렇게 가르켜 봤자 아무런 쓸모가 없다.

마찬가지로 const int _hpConst로 똑같이 해도 아무런 일이 발생하지 않는다.

int&는 누군가를 가르켜야 하는 값이고

const는 절대로 바뀔 수 없는 값인데

선처리 영역에서 만들어졌다면 모든 게임이 끝났고

탄생을 이미 한 상태인데 이미 탄생을 한 상태이기 때문에

생성자 내부 {} 안에서는 다른 값으로 바꿀 수 없다는 것이다.

그래서 int&, const와 같은 녀석을은 탄생함과 동시에

즉, 선처리 영역에서 처리를 해주어야 한다는 것이다.

이렇게


< 참고 >.
모던 C++ 11에서는

이런식으로도 초기화가 가능하기는 하다.


초기화가 시작됨과 동시에 처리 해야할 부분들은 선처리 영역에서 처리를 해주는게 좋다.

이게 C++의 매력이자 마력이다.

profile
https://cjbworld.tistory.com/ <- 이사중

0개의 댓글