CPP Module 04

hhkim·2022년 6월 5일
0

42cursus

목록 보기
13/20
post-thumbnail

ex00 - Polymorphism

개념

다형성(Polymorphism)

모습은 같지만 형태는 다른 것

  • CPP의 가상 함수(virtual 함수), 연산자 오버로딩, 템플릿 등

dynamic_cast

상속관계에 있는 두 포인터 간 캐스팅 지원

  • 캐스팅 오류를 컴파일 타임에 방지하기 위해 사용
Base p;
Derived c;
Base* p_p = &p;
Derived* p_c = dyanmic_cast<Derived*>(p_p);  // 오류!

오버라이딩

자식 클래스에서 부모에게 상속받은 함수를 이름은 같지만 내용은 다른 함수로 재정의하는 것

virtual 함수

  • 부모 클래스의 포인터에 자식 클래스 인스턴스의 주소를 할당하면 부모 클래스의 멤버만 사용 가능 (업캐스팅)
  • 부모 클래스의 함수를 virtual로 지정하면 자식 클래스에서 해당 함수를 오버라이딩하고 이후에 업캐스팅된 경우에도 자식 클래스의 함수를 호출함
    👉 컴파일 타임이 아니라 런타임에 실행될 함수가 결정됨 (동적 바인딩)

  • 가상 함수가 선언된 클래스는 가상 테이블을 가짐
  • 이를 상속받은 클래스에서 가상 함수를 오버라이딩하면 자동으로 가상 함수가 되기 때문에 역시 가상 테이블을 가짐
  • 가상 함수를 호출하면 가상 테이블을 거쳐 호출됨
    👉 가상 함수는 안전하지만 보통 함수보다 성능이 조금 떨어짐

구현

  • 생성자와 소멸자는 메시지를 출력해야 함
    • 모든 클래스에 같은 메시지를 사용하면 안 됨
      👉 부모 클래스의 소멸자를 가상 함수로 설정 (추후 자식 소멸자가 부모 소멸자까지 자동 호출)
      👉 나중에 발생할 문제를 방지하기 위해 소멸자를 항상 가상 함수로 설정하는 것이 좋음

Animal 클래스

부모 클래스

멤버 변수

  • protected std::string type
    • 비어 있어도 되고 원하는 값을 세팅해도 됨

멤버 함수

  • makeSound(): 적절한 소리를 출력해야 함
    👉 자식 클래스에서 오버라이딩하면 될 듯

Dog, Cat 클래스

Animal 클래스 상속

  • type: 자신의 클래스명으로 초기화

WrongAnimal, WrongCat 클래스

  • WrongAnimalmakeSound()virtual 키워드를 추가하지 않음
    👉 이후 테스트에서 WrongCatmakeSound()를 호출해도 WrongAnimalmakeSound()가 호출됨

테스트 코드

int	main(void)
{
	const Animal*	meta = new Animal();
	const Animal*	i = new Cat();
	const Animal*	j = new Dog();

	meta->makeSound();
	i->makeSound();
	j->makeSound();

	delete j;
	delete i;
	delete meta;

	std::cout << std::endl;

	const WrongAnimal*	wrong_meta = new WrongAnimal();
	const WrongAnimal*	k = new WrongCat();

	wrong_meta->makeSound();
	k->makeSound();

	delete k;
	delete wrong_meta;

	return (0);
}

ex01 - I don’t want to set the world on fire

개념

  • 포인터로 정의한 멤버 변수가 있다면 소멸자에서 정리해줘야 함 (자동으로 정리해주지 않음)
  • 얕은 복사를 하는 경우 포인터 멤버 변수를 다른 포인터로 덮어씌우는 과정에서 메모리 누수가 발생하거나 이미 해제된 메모리를 이중으로 해제하는 문제가 생길 수 있음

구현

  • 각 클래스의 생성자와 소멸자가 특정 메시지를 출력해야 함

Brain 클래스

멤버 변수

  • std::string ideas[100]

Dog, Cat 클래스

멤버 변수

  • private Brain* 추가
    • 생성자에서 new Brain()으로 생성
    • 소멸자에서 delete로 삭제

테스트

  • Animal 배열을 만들고 채우기
    • 반은 Dog 반은 Cat
  • 생성된 배열을 반복하면서 모든 Animal 삭제하기
    (각 객체를 Animal *에 할당!)
    • 적절한 소멸자가 순서에 따라 실행되어야 함
  • 메모리 누수 확인
  • DogCat의 복사는 얕으면 안 됨
    👉 Brain*을 복사할 때 깊은 복사를 해야 하므로 복사 생성자와 복사 대입 연산자 수정

ex02 - Abstract class

개념

순수 가상 함수(pure virtual functions)

virtual void	makeSound(void) const = 0;
  • 가상 함수를 구현하지 않고 뒤에 = 0를 붙이면 순수 가상 함수가 됨
  • 순수 가상 함수는 메모리상에 생성되지 않고, 오직 오버라이딩을 위해서만 존재하는 함수
    👉 함수 호출은 물론, 함수를 가지는 클래스의 객체를 생성하는 것도 불가능

추상 클래스(abstract class)

  • 순수 가상 함수를 하나 이상 가지며 반드시 상속되어야 하는 클래스

구현

  • Animal 클래스의 객체를 만들 수 없도록 수정
  • 그래도 모두 이전과 같이 동작해야 함

ex03 - Interface & recap

개념

인터페이스

C++98에는 존재하지 않지만 순수 추상 클래스를 인터페이스라고 부름

  • 추상 클래스(abstract class): 상속을 위한 클래스
  • 구상 클래스(concrete class): 추상 클래스가 아닌 클래스

구현

추상 클래스 AMateria

class AMateria
{
protected:
	...
public:
	AMateria(std::string const & type);
	...
	std::string const	&getType() const; // Returns the materia type
	virtual AMateria*	clone() const = 0;
	virtual void		use(ICharacter& target);
};

구상 클래스 Ice, Cure

  • AMateria 상속
  • type은 소문자로 지정: ice, cure
  • 멤버 함수 clone()은 같은 타입을 리턴
    • Ice에 사용했으면 새로운 Ice 인스턴스를 리턴

use(ICharacter&)

각각 아래의 내용을 출력 (<name>은 인자로 전달된 캐릭터의 이름)

  • Ice: * shoots an ice bolt at <name> *
  • Cure: * heals <name>’s wounds *

Materia를 다른 것에 할당할 때, type을 복사하지 말 것

인터페이스 ICharacter

class ICharacter
{
public:
	virtual ~ICharacter() {}

	virtual std::string const	&getName() const = 0;

	virtual void	equip(AMateria* m) = 0;
	virtual void	unequip(int idx) = 0;
	virtual void	use(int idx, ICharacter& target) = 0;
};

구상 클래스 Character

  • ICharater 상속
  • 인벤토리에 슬롯 4개를 가짐: 최대 4개의 Materia를 가질 수 있음
  • 인벤토리는 생성될 때 비어있음
  • equip(): Materia를 슬롯의 0~3번째 순서대로 장착
  • 꽉 찬 인벤토리에 추가하려고 하는 경우나 존재하지 않는 Materia를 use, unequip하려고 하는 경우 아무것도 하지 않음
  • unequip(): Materia 버리기 (Materia를 삭제하면 안 됨)
    • 바닥에 버린 Materia는 마음대로 처리할 것
    • unequip() 호출 전 주소를 저장해두거나 할 것
    • 메모리 누수가 발생하면 안 됨
  • use(int, ICharacter&): slot[idx]AMateria::use()target을 넘겨줌
  • 이름을 인자로 갖는 생성자
  • 모든 종류의 복사(복사 생성자, 복사 대입 연산자)는 깊은 복사여야 함
    • 이 과정에서 기존의 Materia들은 새로운 것이 추가되기 전에 삭제되어야 함
  • 캐릭터가 삭제될 때도 Materia들이 삭제되어야 함

캐릭터의 인벤토리는 모든 종류의 AMateria를 지원해야 함

인터페이스 IMateriaSource

class IMateriaSource
{
public:
	virtual ~IMateriaSource() {}

	virtual void		learnMateria(AMateria*) = 0;
	virtual AMateria*	createMateria(std::string const &type) = 0;
};

구상 클래스 MateriaSource

  • IMateriaSource 상속
  • Materia의 템플릿을 learn하고 필요할 때 create하는 역할

learnMateria(AMateria*)

  • 인자로 전달된 Materia를 복사해서 나중에 clone()할 수 있도록 메모리에 저장
  • 최대 4개의 Materia를 가질 수 있음
  • 각 Materia들이 유일할 필요는 없음 (중복 가능)

createMateria(std::string const &)

  • 새로운 Materia 리턴
  • 이전에 learnMateria()된 Materia와 인자로 전달된 type이 동일하면 클론해서 리턴, type을 알 수 없으면 0 리턴

클래스 전방 선언

헤더 파일이 서로를 참조하는 경우 사용

class ICharacter;
class AMateria { ... };

테스트 코드

int	main()
{
	IMateriaSource*	src = new MateriaSource();

	src->learnMateria(new Ice());
	src->learnMateria(new Cure());

	ICharacter*	me = new Character("me");
	AMateria*	tmp;

	tmp = src->createMateria("ice");
	me->equip(tmp);
	tmp = src->createMateria("cure");
	me->equip(tmp);

	ICharacter*	bob = new Character("bob");
	me->use(0, *bob);
	me->use(1, *bob);

	delete bob;
	delete me;
	delete src;

	return (0);
}

0개의 댓글