cpp module 04

slee2·2021년 11월 3일
0

42Seoul

목록 보기
13/15
post-thumbnail
post-custom-banner

ex00

각 클래스의 생성자와 소멸자는 특정한 출력을 해야한다.

Animal 클래스를 만들어라

  • std::string type; (protected)
  • void makeSound();

Animal을 상속받는 Dog 클래스를 만들어라
Animal을 상속받는 Cat 클래스를 만들어라

  • Animal 클래스를 상속받는다.
  • void makeSound(); (각 클래스에서 다른 소리를 내야한다.)

동일한 조건으로 WrongAnimal과 WrongCat클래스를 만들어라

  • void makeSound();
    WrongAnimal클래스의 makeSound() 메서드가 사용된다.

main

int main()
{
	const Animal* meta = new Animal();
	const Animal* j = new Dog();
	const Animal* i = new Cat();
	std::cout << j->getType() << " " << std::endl;
	std::cout << i->getType() << " " << std::endl;
	i->makeSound(); //will output the cat sound!
	j->makeSound();
	meta->makeSound();
	...
}

가상 소멸자

가상 소멸자는 클래스를 포인터로 관리를 할때 많이 사용한다.

프로젝트의 예시대로 Animal 클래스, 그 클래스를 상속받는 Dog, Cat클래스를 만든 후, main을 실행하면 어떠한 문제를 발견할 수 있다.

 const Animal* j = new Dog();
 delete j;

위 경우 Animal 클래스의 소멸자만 실행된다. 즉, Dog소멸자가 실행되지 않는 점에서 누수가 발생하게 된다. 이를 방지하기 위해 가상 소멸자라는 것을 사용한다.

virtual ~Animal();

위처럼 소멸자 앞에 virtual을 붙이게 되면 Dog소멸자 -> Animal소멸자 순서로 실행이 된다.
간단히 사진으로 확인해보자

	const Animal* meta = new Animal();
	const Animal* j = new Dog();
	const Animal* i = new Cat();
	delete meta;
	delete j;
	delete i;

가상 소멸자를 사용안했을 경우

가상 소멸자를 사용했을 경우

가상 함수

가상 함수는 실행시간(런타임)에 그 값이 결정됩니다.(후기 바인딩) 즉, 컴파일러가 실행되는 중에는 가상 함수에 들어가지 않는 것입니다.

쉽게 말해 부모 클래스에 virtual void makeSound(); 가 있고,
자식에 void makeSound()를 만든 다음,
메인에서 Animal *a = new Cat(); 으로 받고 a.makeSound()를 하면,
컴파일러는 Animalvirtual을 보는 것이 아니라 포인터를 따라가
CatmakeSound() 를 본다고 생각하면 됩니다.

Animal의 메서드에 virtual이 붙지 않았을 경우에는 컴파일러가
AnimalmakeSound()를 먼저 보게 됩니다.

이는 WrongAnimal과 Animal의 차이점이 가상함수 사용유무라고 생각하면 된다.

makeSound()를 사용했을때,
Dog 는 Dog, Cat 은 Cat, Animal 은 Animal의 각각의 makeSound()를 실행하면 된다. 참고로 일반적으로 오버로딩을 하게 되면 Animal로 받았기 때문에 Dog, Cat, Animal 모두 Animal::makeSound()가 실행된다.

하지만 여기서 virtual void makeSound(); 같이 작성한다면 각각의 오버로딩된 메서드가 실행된다.

WrongAnimal은 virtual을 넣지않고 실행한 후 비교를 하면 끝.

Animal.hpp

#ifndef ANIMAL_HPP
# define ANIMAL_HPP

#include <iostream>

class Animal {
    protected:
	std::string type;
    public:
	Animal();
	virtual ~Animal();
	virtual void makeSound(void) const;
	std::string getType(void) const;
	Animal& operator=(Animal const &c);
	Animal( const Animal& a);
};

#endif

WrongAnimal.hpp

#ifndef WRONGANIMAL_HPP
# define WRONGANIMAL_HPP

#include <iostream>

class WrongAnimal {
    protected:
	std::string type;
    public:
	WrongAnimal();
	virtual ~WrongAnimal();
	WrongAnimal(WrongAnimal const &wronganimal);
	void makeSound(void) const;
	std::string getType(void) const;
	WrongAnimal& operator=(WrongAnimal const &d);
};

#endif

ex01

이전 파일을 그대로 가져오고,

Brain 클래스를 만들어라

  • Cat과 Dog는 private으로 Brain*를 가지고 있어야 한다.
  • Cat과 Dog는 생성자에서 Brain()를 이용해 새로운 Brain*를 받아야한다.
  • Cat과 Dog는 소멸자에서 Brain를 지워야한다.
  • Cat과 Dog는 Brain을 받고 깊은 복사를 할 수 있어야 한다.
  • Brain은 ideas라고 불리는 100 크기의 std::string 배열을 갖고 있어야 한다.

main

int main()
{
 	const Animal* j = new Dog();
 	const Animal* i = new Cat();
 	delete j;//should not create a leak
 	delete i;
  
 	Animal	*a[4];

	for(int i=0; i<4; i++) {
		if (i < 2) {
			a[i] = new Cat();
		}
		else {
			a[i] = new Dog();
		}
	}
	for(int i=0; i<4; i++) {
		a[i]->makeSound();
	}
	
	for(int i=0; i<4; i++) {
		delete a[i];
	}
	
	Brain brain;
	Cat cat(brain);
	Dog dog(brain);

	brain.setter("brain", 100);
	cat.setter("cat", 100);
	dog.setter("dog", 100);

	std::cout << brain.getter() << std::endl;
	std::cout << cat.getter() << std::endl;
	std::cout << dog.getter() << std::endl;

	return 0;
}

Cat 추가

Cat::Cat(Brain const &brain) {
	*this->b = brain;
}

이렇게 끝낸다면 얕은 복사가 된다. 그러므로 brain 클래스에서 연산자를 추가해줘야 한다.

Brain::operator

Brain& Brain::operator=(Brain const &br) {
	std::cout << "Assignation operator called" << std::endl;
	Brain *a = new Brain();
	for(int i=0; i<100; i++) {
		a->ideas[i] = br.ideas[i];
	}
	return *a;
}

이렇게 되면 *this->b = brain;을 할때 operator로 넘어가 깊은 복사를 할 수 있다.

ex02

기본 Animal 클래스를 인스턴스화 하지 못하게 만들어라.

순수 가상 함수

순수 가상 함수란, virtual로 명시된 함수를 =0을 붙임으로서 해당 함수를 정의하지 않겠다는 의미이다. 인터페이스의 개념으로 생각하면 편해진다.

사용방법은 간단하다.

virtual void makeSoune() const = 0;

이러면 해당 메서드를 가지고 있는 클래스는 인터페이스가 된다.

예를들어 Animal 클래스에 저런식으로 메서드를 만들었다면,

int main()
{
	Animal *a = new Animal();
}

에서 오류가 나온다. 즉, Interface로 만들어진 Animal클래스는 자신 자체를 객체로 만들 수 없다.

ex03

후... 이거 못막습니다..

AMateria 클래스를 만들어라.

Ice와 Cure 클래스를 만들어라. (AMateria 상속)

  • AMateria clone(); // AMateria 복사
  • void use(ICharacter&)
    Ice -> " shoots an ice bolt at NAME "
    Cure -> " heals NAME’s wounds "
    NAME -> ICharacter에 있음

ICharacter 클래스를 만들어라.(Interface)

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 클래스를 만들어라. (ICharacter 상속)

  • 최대 4개의 AMateria를 보유하고 있으며, 0~3순으로 사용
  • equip을 할때 4개 모두 보유하고 있으면 못해야함.
  • use/unequip 할때 무기가 아예 없으면 못해야함.
  • unequip은 delete를 하면 안된다.
  • use를 할때, AMateria::use로 target을 보내야한다.

IMateriaSource 클래스를 만들어라. (Interface)

class IMateriaSource
{
	public:
	virtual ~IMateriaSource() {}
	virtual void learnMateria(AMateria*) = 0;
	virtual AMateria* createMateria(std::string const &type) = 0;
};

MateriaSource 클래스를 만들어라. (IMateriaSource 상속)

  • LearnMateria는 매개변수로 전달된 Materia를 Character와 같은 방식으로 저장.
  • createMateria는 매개변수로 전달된 type이 가지고 있는 Materia를 반환.

main

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;
}
post-custom-banner

0개의 댓글