각 클래스의 생성자와 소멸자는 특정한 출력을 해야한다.
Animal 클래스를 만들어라
Animal을 상속받는 Dog 클래스를 만들어라
Animal을 상속받는 Cat 클래스를 만들어라
동일한 조건으로 WrongAnimal과 WrongCat클래스를 만들어라
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()
를 하면,
컴파일러는Animal
의virtual
을 보는 것이 아니라 포인터를 따라가
Cat
의makeSound()
를 본다고 생각하면 됩니다.
Animal
의 메서드에virtual
이 붙지 않았을 경우에는 컴파일러가
Animal
의makeSound()
를 먼저 보게 됩니다.
이는 WrongAnimal과 Animal의 차이점이 가상함수 사용유무라고 생각하면 된다.
makeSound()를 사용했을때,
Dog 는 Dog, Cat 은 Cat, Animal 은 Animal의 각각의 makeSound()를 실행하면 된다. 참고로 일반적으로 오버로딩을 하게 되면 Animal로 받았기 때문에 Dog, Cat, Animal 모두 Animal::makeSound()가 실행된다.
하지만 여기서 virtual void makeSound();
같이 작성한다면 각각의 오버로딩된 메서드가 실행된다.
WrongAnimal은 virtual을 넣지않고 실행한 후 비교를 하면 끝.
#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
#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
이전 파일을 그대로 가져오고,
Brain 클래스를 만들어라
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(Brain const &brain) {
*this->b = brain;
}
이렇게 끝낸다면 얕은 복사가 된다. 그러므로 brain 클래스에서 연산자를 추가해줘야 한다.
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로 넘어가 깊은 복사를 할 수 있다.
기본 Animal 클래스를 인스턴스화 하지 못하게 만들어라.
순수 가상 함수란, virtual
로 명시된 함수를 =0
을 붙임으로서 해당 함수를 정의하지 않겠다는 의미이다. 인터페이스
의 개념으로 생각하면 편해진다.
사용방법은 간단하다.
virtual void makeSoune() const = 0;
이러면 해당 메서드를 가지고 있는 클래스는 인터페이스가 된다.
예를들어 Animal
클래스에 저런식으로 메서드를 만들었다면,
int main()
{
Animal *a = new Animal();
}
에서 오류가 나온다. 즉, Interface로 만들어진 Animal클래스는 자신 자체를 객체로 만들 수 없다.
후... 이거 못막습니다..
AMateria 클래스를 만들어라.
Ice와 Cure 클래스를 만들어라. (AMateria 상속)
AMateria clone();
// AMateria 복사void use(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 상속)
IMateriaSource 클래스를 만들어라. (Interface)
class IMateriaSource
{
public:
virtual ~IMateriaSource() {}
virtual void learnMateria(AMateria*) = 0;
virtual AMateria* createMateria(std::string const &type) = 0;
};
MateriaSource 클래스를 만들어라. (IMateriaSource 상속)
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;
}