[C++기초] 7. 객체지향 프로그래밍3
INDEX
상속
상속이란?
- 다른 클래스의 특성들을 내려받음
- 베이스 클래스(부모 클래스)
- 파생 클래스(자식 클래스)
- 파생 클래스의 개체는 다음 것들을 가짐
- 베이스 클래스의 멤버 변수
- 베이스 클래스의 멤버 메서드
- 자신의 생성자와 소멸자
- 파생클래스는 멤버변수 및 메서드 추가가능
파생 클래스의 접근 제어자
- Java와 달리 상속 시 베이스 클래스의 메버의 접근수준을 결정할 수 있다
- public 상속
- 그대로 가져옴
- protected 상속
- public -> protected로 변경
- private 상속
- public/protected -> private로 변경
예시
//Animal.h class Animal { public: Animal(int age); private: int mAge; }; //Cat.h class Cat : public Animal { public: Cat(int age, const char* name); private: char* mName; }; //Cat.cpp Cat::cat(int age, const char* name) :Animal(age) { size_t size = strlen(name) +1; mName = new char[size]; strcpy(mName, name); }
- public 상속
생성자 호출 순서, 소멸자 호출 순서
생성자 호출 순서
-
부모 생성자 -> 자식 생성자
-
베이스 클래스의 생성자가 먼저 호출됨
- 명시적 또는 암시적으로
-
그 다음 파생 클래스의 생성자가 호출됨
-
부모 클래스의 특정 생성자를 호출할 때는 초기화 리스트를 사용해야 함
-
유의사항 : 암시적으로 부모 클래스를 호출할 때, 없으면 문제가 생길 수 있음
//Animal.h class Animal { public: Animal(int age); private: int mAge; } //Cat.cpp Cat::cat(int age, const char* name) :Animal(age) //암시적으로 Animal()을 호출 //하지만 Animal()이 없음 -> 컴파일에러!!!! { size_t size = strlen(name) +1; mName = new char[size]; strcpy(mName, name); }
소멸자 호출 순서
- 자식 소멸자 -> 부모 소멸자
- 생성자 호출순서와 정반대
- 파생클래스 소멸자의 마지막에서 베이스 클래스의 소멸자가 자동적으로 호출됨
다형성
멤버함수의 메모리
-
멤버 함수도 메모리 어딘가에 위치해 있음
-
- 모든 것이 메모리 어딘가에 위치해 있어야 함
-
그런데 각 개체마다 멤버 함수의 메모리가 잡혀 있지 않음
-
멤버 함수는 컴파일 시에 딱 한 번만 메모리에 할당됨
-
- 저수준에서는 멤버 함수는 전역 함수와 그다지 다르지 않음
함수 오버라이딩
- 함수의 정의는 같지만, 동작은 다름
다형성 정적 바인딩
정적바인딩 - 멤버변수
Cat* yourCat = new Cat(5, "Mocha"); youtCat.Speak(); //Cat클래스의 메서드 실행 Animal* yourCat = new Cat(5, "Mocha"); yourCat.Speak(); //Animal 클래스의 메서드 실행
다형성 동적 바인딩
다형성 동적 바인딩
-
C++에서 자바처럼 다형성 함수 동작하게 해줌
-
가상함수를 사용
//Animal.h class Animal { public: virtual void Move(); virtual void Speak(); } //Animal.cpp void Animal::Move() { } void Animal::Speak() { stdd<<cout<<"An animal is speaking"<<std::endl; }
//Cat.h class Cat : public Animal { public: void Speak(); } //cat.cpp void Cat::Speak() { std::cout<<"Meow"<<std::endl; }
//Main.cpp Cat* myCat = new Cat(2, "Coco"); myCat->Speak(); // "Meow" Animal* yourCat = new Cat(5, "Mocha"); yourCat->Speak(); // "Meow"
Java는 모든 것이 기본적으로 가상함수
-
때문에 C++처럼 하지 않아도 저렇게 돌아감
-
Java프로그래머는 final 키워드를쓸 수 있음
- 이걸 까먹으면 비 가상함수보다 언제나 느림
C++에서는 virtual 키워드를 생략하면 정말 개판이 날 수 있다!!
가상함수
- 자식 클래스의 멤버변수가 언제나 호출됨
- 부모의 포인터 또는 참조를 사용 중이더라도
- 동적 바인딩/ 늦은 바인딩
- 실행 중에 어떤 함수를 호출할 지 결정한다
- 당연히 정적바인딩보다 느림
- 이들을 위해 가상 테이블이 생성됨
- 모든 가상 멤버함수의 주소를 포함
- 클래스마다 하나!
- 개채를 생성할 때, 해당 클래스의 가상 테이블 주소가 함께 저장됨
- 정적 테이블 또는 룩업 테이블이라고도 불림
다형성 가상 소멸자
가상 소멸자는 모든 클래스마다 넣어야 한다!!! (매우 중요!!)
소멸자 주의해야 할 점!
virtual 키워드 생략하면 안된다! (메모리 누수날 수 있음)
잘못된 예제
//Animal.h class Animal { public: ~Animal(); private: int mAge; }; //Cat.h class Cat : public Animal { public: virtual ~Cat(); private: char* mName; }; //Cat.cpp Cat::~Cat() { delete mName; }
제대로 된 예제
//Animal.h class Animal { public: virtual ~Animal(); private: int mAge; }; //Cat.h class Cat : public Animal { public: virtual ~Cat(); private: char* mName; }; //Cat.cpp Cat::~Cat() { delete mName; }
c++14/17에는 소멸자 가상함수 외에 다른 해결책이 있다.
다중 상속
- Java,C#에는 다중상속 없음
- 여러개의 부모를 가짐
예시
class TA : public Student, public Faculty { }; TA* myTa = new TA();
위의 코드의 경우 어느 부모의 생성자가 먼저 호출될까?
파생 클래스에서 등장한 부모 클래스 순서대로
class TA : public Student, public Faculty
-> Studet(), Faculty() 순으로 호출class TA :public Faculty, public Student
-> Faculty(), Student() 순으로 호출생성자의 순서를 지정할 수도 있다.
class TA : public Studetn, public Faculty : Faculty() , Student() { };
Faculty(), Student()순으로 호출
Java에서처럼 super()를 쓸 수 없는 이유
다중 상속이 가능하기 때문
부모가 같은 이름의 메서드를 가질 경우 어떻게 호출하나?
어떤 함수가 호출될 지 모호할 때는 우리가 직접 부모 클래스를 특정해줘야함.
myTA->Studetn::DisplayData();
다이아몬드 문제
Q : Liger안에는 몇 개의 Animal이 있나?
- A : 2개
Q : 그럼 그 문제는 어떻게 해결하나?
- A : 가상 베이스 클래스를 사용한다
- But 근본적인 해결책이라고 하기엔 모호하다!
- 이런걸 미리 신경써야 하는게 이상하다
- 먼 훗날에 혼종이 나올거라는 걸 미리 알기 힘들다
- So 다중상속을 최대한 쓰지 맙시다.
- 대신 인터페이스를 쓰면 됩니다.
추상 클래스
추상클래스
- 구체적이지 않은 클래스
- 함수의 구현체가 있는 클래스
- 순수 가상함수를 가지고 있는 베이스 클래스
- 추상 클래스에서 개체를 만들 수 없음
- 추상 클래스를 포인터나 참조형으로는 사용 가능
예시
Animal.h
//Animal.h class Animal { public: virtual ~Animal(); //함수는 있지만 구현체는 없다는 의미 virtual void Speak() = 0; private: int mAge; };
Cat.h
//Cat.h class Cat : public Animal { public: ~Cat(); void Speak(); private: char* mName; };
main.cpp
Animal myAnimal; //불가능 Animal* myAnimal = new Animal(); //불가능 Animal* myCat = new Cat(); //OK Animal& myCatRef = *myCat; //OK
순수 가상함수
- 구현자가 없는 멤버 함수
- 파생 클래스가 구현해야함
example
//Animal.h class Animal { public: virtual void Speak() = 0; private: int mAge; }; //Cat.h class Cat : public Animal { public: Cat(int age, const char* name); void Speak(); private: char* mName; }; //컴파일 에러 //Speak()이 구현 안 되어 있음
순수 가삼함수를 선언하는 법
virtual <return-type> <function-name> (<argument-list>) = 0;
virtual void Speak() = 0;
virtual float GetArea() = 0;
인터페이스
- C++은 자체적으로 인터페이스를 지원하지 않음
- 순수 추상 클래스를 사용해서 Java의 인터페이스를 흉내냄
- 순수 가상 함수만 가짐
- 멤버 변수는 없음