각 클래스의 생성자와 소멸자는 특정한 출력을 해야한다.
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;
}