C++는 객체 지향 언어입니다.
이러한 객체 지향 언어는 '캡슐화'라는 특징이 있습니다.
어떠한 기능을 위해 클래스를 만들면,
데이터를 저장할 멤버들을 구성하고
데이터를 다루기 위해 멤버 함수들을
구현하였죠. 이 모든 것이 모여 하나의 클래스가 되죠.
이것이 바로 캡슐화입니다.
캡슐화는 '은닉성'이라는 특징과 이어집니다.
private, protected, private과 같은 접근 제한자를 통해
멤버 변수 또는 함수들을 공개하거나 보호할 수 있었죠?
이것이 바로 은닉성입니다.
또한 객체 지향 언어의 특징으로 '상속'이 있습니다.
아래 코드가 상속을 구현한 것이죠.
cParent라는 클래스를 만든 후,
cChild라는 클래스를 만들어 cParent 클래스를
상속받았습니다. 21번째 줄에서 볼 수 있듯이
상속받을 클래스의 이름을 접근 제한자와 함께
적어 주어 상속받을 수 있죠.
cParent 클래스가 부모 클래스,
cChild 클래스가 자식 클래스가 되는 겁니다.
만약 두 클래스를 통해 각각 객체를 만들면
위 그림처럼 구성될 겁니다.
자식 클래스인 cChild로 만든 객체는 cParent 클래스가 갖고 있는 멤버와
cChild 클래스가 갖고 있는 멤버를 모두 갖고 있게 되죠.
cParent 클래스를 통해 만든 객체의 크기는 4Byte일 것이고,
cChild 클래스를 통해 만든 객체의 크기는 8Byte일 겁니다.
27번째 줄에서 볼 수 있듯이
자식 클래스에 멤버 함수를 하나 추가했습니다.
정수를 하나 받아서 멤버 m_i에
값을 대입하는 함수이죠.
근데 오류가 발생합니다.
이유는 부모 클래스를 그대로 물려받은
자식 클래스라고 하더라도 부모 클래스의 멤버 m_i는
private로 선언되었기 때문이죠.
아래 코드처럼
부모 클래스의 멤버 변수를 public으로
바꿔 선언해 주면 자식 클래스에서 접근하여
값을 바꾸는 것이 가능합니다.
근데 이러면
main 함수와 같은 외부에서도 멤버에
직접 접근이 가능하여 문제가 발생할 여지가 있죠.
이를 위해 자식 클래스에서는 접근할 수 있지만 외부에서는
접근할 수 없게 하는 접근 제한자가 바로 'protected'입니다.
9번째 줄처럼
m_i를 protected로 선언하자,
자식 클래스에서 m_i에 접근할 수 있지만
main 함수에서는 m_i에 접근할 수 없는 것이 보입니다.
부모 클래스를 상속받은
자식 클래스의 자식 클래스를 만들었습니다.
이때 cChildChild 클래스로 객체를 만들면
해당 객체는 메모리에 어떻게 할당될까요?
아래 그림처럼
부모의 부모 클래스, 부모 클래스, 자신순으로 할당이 될 겁니다.
(cChildChild 클래스는 지우겠습니다.)
부모 클래스에 m_i에 인자로 받은 값을 대입하는
함수를 구현하였습니다.
해당 함수를 아래 코드처럼
자식 클래스로 만든 객체는 부모 클래스의
멤버 함수를 사용할 수 있겠죠.
51번째 코드의 의미는
cChild 클래스로 만든 child 객체는
cParent 부분과 cChild 부분이 있는데,
그 중 cParent 부분에 존재하는 m_i에
10을 대입하겠다는 거겠죠?
부모 클래스의 멤버 m_i는 protected로 선언이
되었으니까 자식 클래스에서 초기화해 줄 수도
있겠다는 생각이 들 수 있습니다.
그래서 자식 클래스의 생성자를 통해
m_i를 초기화하려고 하니 오류가 발생합니다.
왜냐하면 부모 클래스의 멤버는 부모 클래스의 생성자로,
자식 클래스의 멤버는 자식 클래스의 생성자로
초기화해야 하기 때문입니다.
각자 멤버는 각자 초기화하기로 되어 있는 겁니다.
근데 아래 코드처럼
대입하는 것은 가능합니다. 아무래도 m_i는
protected로 선언되었기 때문에 접근하는 것은
가능합니다. 그러나 초기화만큼은 허락하지 않는 것이죠.
그럼 여기서 궁금한 점이 생깁니다.
자식 클래스로 객체를 만들면
해당 객체는 부모 클래스의 요소와
자신의 클래스의 요소를 모두 갖게 되죠.
그럼 부모 클래스의 생성자가 먼저 호출될까요,
아니면 자식 클래스의 생성자가 먼저 호출될까요?
메모리상 앞에 있는 부모 클래스의
생성자가 먼저 호출될까요?
정확히 말씀드리자면 호출은 자식 클래스의 생성자가 먼저 되고
이후 부모 클래스의 생성자가 호출되며 동작이 먼저 완료되므로
초기화는 부모 클래스가 먼저 됩니다.
아래 코드를 보시죠.
각 클래스의 생성자가 호출될 때
어떤 생성자가 호출되었는지 알려 주기 위해
문자열을 출력하도록 하였습니다.
이후 자식 클래스로 객체를 만들어 결과를
확인해 보니 부모 -> 자식순으로 생성자 호출
문자열이 출력되는 것이 보입니다.
그럼 이를 두고 부모 클래스의 생성자가
먼저 호출되었다고 말할 수 있을까요?
그럼 이번에는 아래와 코드와 같은
함수가 있다고 하죠.
main 함수에서 funcB 함수를 호출하였습니다.
그럼에도 불구하고 콘솔 창에는 funcA 함수가
먼저 호출된 것처럼 표현되고 있죠.
호출은 funcB 함수가 먼저 되었지만,
funcB 함수의 동작이 완료되기 전에
funcA 함수가 호출되어 funcA 함수의
동작이 먼저 완료된 후 funcB 함수의 동작이
완료되었기 때문에 콘솔 창과 같은 결과가 나오는 것이죠.
호출 자체는 funcB 함수가 먼저 되었는데도 말이죠.
자식 클래스로 객체를 만드는 과정을
디버깅해 보겠습니다. 객체 생성하는 부분에서
F11을 눌러 디버깅을 해 보면
화살표가 자식 클래스 생성자 부분으로 이동합니다.
호출 스택에서도 보시면 31번째 줄,
자식 클래스의 생성자 부분을 먼저 호출하는 것을
확인할 수 있습니다.
디버깅을 더 진행해 보죠.
이후 부모 클래스의 생성자로 가서
부모 클래스의 멤버인 m_i를 0으로
초기화하는 것을 확인할 수 있습니다.
이는
호출 스택에서도 확인할 수 있죠.
31번째 줄에 있는 자식 클래스의 생성자가
호출된 이후, 그 위에 부모 클래스의 생성자가 있는
16번째 줄 스택이 쌓여 있는 모습입니다.
디버깅을 더 진행해 보면
부모 클래스의 생성자로 가서
부모 클래스의 멤버를 초기화하고
이후 다시 자식 클래스의 생성자로
돌아와 자식 클래스의 멤버를 초기화하는 모습입니다.
호출 스택을 보면
부모 클래스의 생성자인 16번째 줄로 갔던
호출 스택이 사라지고 자식 클래스의 생성자가 있는
32번째 줄이 호출 스택에 남아 있네요.
이제 자식 클래스의 생성자가 먼저 호출되고, 이후 부모 클래스의 생성자가
호출되며 동작이 먼저 완료되므로 초기화는 부모 클래스가 먼저 된다는 것이
어떤 것이 느낌이 오시나요?
아래 그림처럼 상속이 진행된
클래스로 객체를 만들면
위와 같이 자식 클래스의 생성자가 먼저 호출되고
부모 클래스가 없을 때까지 부모 클래스의 생성자가
쭉 호출될 겁니다. 그리고 가장 위에 있는 부모 클래스의
멤버부터 초기화가 완료되면서 다시 돌아와
최종적으로는 마지막 자식 클래스의 초기화가 완료되겠죠.
자식 클래스의 생성자에는
위 코드처럼 부모 클래스의 생성자를
호출하는 부분이 생략되어 있는 겁니다.
심지어 이 부분을
반대로 작성해서 자식 클래스의 멤버부터
초기화하려고 해도 그 전에 부모 클래스의
생성자로 올라가 버리죠.
아래 코드에서 19번째 줄을 보시죠.
받은 인자로 멤버를 초기화하고 싶다면
생성자 오버로딩을 통해서 초기화를 진행했었죠?
이때 자식 클래스에서, 인자를 받는 부모 클래스 생성자를
호출하고 싶다면 36번째 줄처럼 명시를 해 줘야 합니다.
이처럼 명시를 해 주면 부모 클래스의 생성자를 호출할 때
정수를 하나 받는 생성자로 가서 실행을 하겠죠.
만약 명시를 해 주지 않는다면
부모 클래스의 기본 생성자가 호출이 될 겁니다.
강의 출처 : https://www.youtube.com/watch?v=vlWcwPFnHIQ&list=PL4SIC1d_ab-aOxWPucn31NHkQvNPHK1D1&index=81
'C++ > 기초' 카테고리의 다른 글
C++ 기초 : 상속 (2) (0) | 2024.05.04 |
---|---|
C++ 기초 : 오버라이딩 (0) | 2024.05.03 |
C++ 기초 : tree (10) (0) | 2024.05.03 |
C++ 기초 : tree (9) (0) | 2024.05.02 |
C++ 기초 : tree (8) (0) | 2024.05.01 |