ex00
다형성(polymorphism)
가상 함수
- 동적 바인딩을 사용하고 싶은 함수에 virtual 키워드를 붙여 가상함수로 만들고 파생 클래스에서 오버로딩하여 기반클래스 포인터에 파생클래스를 연결해서 사용해도 올바른 함수가 출력될 수 있도록 한다.
ex01
파생 클래스의 private 변수를 기반 클래스 포인터로 사용할 수 있으려면?
- 이번 문제에선 파생 클래스인 Dog, Cat의 private 변수로
Brain *
을 만들고 생성자에서 new로 할당해주라고 하고 있다.
- 기반 클래스의 protected변수로
Brain *
을 만들고 상속 받아오려고 보니, protected 변수를 private 변수로 받아오는 방법은 접근제한자를 private으로 쓰는 수밖에 없는데 그럼 public 멤버함수들도 private으로 받아와져 버리게 되는 문제가 생겼다.
- 따라서 private
Brain *
변수는 파생클래스 각각에서 선언할 수 밖에 없다.
- 그런데 이번 과제는 main에서
Animal *
배열을 만들고 기반 클래스 포인터
에 파생 클래스 객체
를 할당해서 사용해 보는 게 목적이기 때문에 기반 클래스 포인터로도 파생 클래스의 brain에 접근할 수 있어야 했다.
- 따라서 Brain 관련 함수를 기반 클래스에서 virtual 함수로 선언은 해두고 파생클래스에서 오버라이딩 해줘야
Animal *
에 new Cat()
, new Dog()
로 할당했을 때도 파생클래스의 private 변수에 접근할 수 있다.
virtual Brain *getBrain(void) const = 0;
기반 클래스 포인터로 파생 클래스 복사
- 깊은 복사를 해야 하기 때문에 Brain 멤버변수를 Brain 클래스의 복사생성자로 할당해준다.
Cat::Cat(const Cat &rhs) : Animal(rhs)
{
this->brain = new Brain(*rhs.getBrain());
std::cout << "Cat copy constructor called" << std::endl;
}
- 대입 연산자 오버로딩의 경우 기존에 brain 멤버변수가 할당 되어 있으면 delete로 해제해주고 나서 다시 생성해 줘야 한다. 또한 같은 객체가 대입 될 수 없도록(자기 자신의 brain을 delete해버리고 다시 복사생성자를 사용할 때 오류가 생김) 예외 처리를 해주어야 한다.
Cat &Cat::operator=(const Cat &rhs)
{
if (this != &rhs)
{
Animal::operator=(rhs);
if (this->brain)
delete this->brain;
this->brain = new Brain(*rhs.getBrain());
std::cout << "Cat assignment operatior called" << std::endl;
}
return (*this);
}
ex02
추상클래스, 순수가상함수
class Animal {
public:
Animal() {}
virtual ~Animal() {}
virtual void speak() = 0;
};
- 순수 가상함수는 무조건 파생클래스에서 오버로딩해야만 사용할 수 있다.
- 순수 가상함수가 포함된 클래스를 추상 클래스라고 부른다.
- 추상 클래스는 객체를 만들 수 없다.
ex03
Interface
- 인터페이스는 특정 기능을 구현할 것을 약속한 추상 형식을 말한다.
출처: https://ehclub.co.kr/2136 [언제나 휴일:티스토리]
- c++ 에는 인터페이스 형식이 없지만 순수 가상 메소드를 이용해 구현할 수 있다.
Abstract class vs Interface class
- 과제에서 순수가상함수를 가지고는 있지만 interface는 아닌 경우 == 추상클래스인 경우 클래스명에 A를 붙이고, 모든 메소드가 정의 없이 순수가상함수로 이뤄진 것만 클래스명에 I를 붙임. 이게 차이인듯
concrete class
- 추상클래스와 달리 모든 연산에 대한 정의가 구현되어 있는 클래스
const 변수 초기화
- C++98에서 클래스 안에서 const 변수는 초기화 불가능
static const 변수 초기화
AMateria.hpp
# include <iostream>
# include <string>
# include "ICharacter.hpp"
class ICharacter;
class AMateria
{
protected:
std::string type;
public:
AMateria(std::string const &type);
virtual ~AMateria(void) {}
std::string const &getType() const;
virtual AMateria *clone() const = 0;
virtual void use(ICharacter &target) { (void) target; }
};
Character.hpp
# include "ICharacter.hpp"
class Character : public ICharacter
{
private:
std::string name;
static const int inventory_size = 4;
AMateria *inventory[Character::inventory_size];
int n_of_equip;
Character(void);
public:
Character(std::string name);
Character(const Character &rhs);
~Character();
Character &operator=(const Character &rhs);
std::string const &getName() const;
void equip(AMateria *m);
void unequip(int idx);
void use(int idx, ICharacter &target);
};
- private 변수로 name과 인벤토리 배열을 가짐.
- 주의할 점
- 생성자에서 inventory 배열 목록 초기화. 각각 널포인터 연결해준다.
- 복사생성자, 대입연산자로 새로운 inventory목록 복사해 오는 경우 기존 inventory에 할당돼있던 AMateria 객체는 해제(delete)해줘야 한다.
- unequip은 delete를 사용하지 않는다.
- equip은 inventory 중 먼저 나오는 empty 배열에 Materia를 저장한다.
- unequip은 inventory의 idx번째 Materia를 NULL로 초기화해줬다. 이 때 delete는 하지 않기 때문에 외부에서 사용시 포인터 주소를 먼저 복사해뒀다가 delete를 따로 해줘야 한다.
- use는 AMateria의 use함수를 사용하고 사용한 Materia를 unequip하는 식으로 구현했다.
- 깊은 복사를 위해 복사생성자와 대입연산자 오버로딩에서 Materia의 clone함수를 사용할 수 있도록 변경.
MateriaSource.hpp
# include <iostream>
# include "IMateriaSource.hpp"
class MateriaSource : public IMateriaSource
{
private:
static const int materias_size = 4;
int n_of_learned;
AMateria *materias[MateriaSource::materias_size];
public:
MateriaSource(void);
MateriaSource(const MateriaSource &rhs);
~MateriaSource(void);
MateriaSource &operator=(const MateriaSource &rhs);
void learnMateria(AMateria *m);
AMateria *createMateria(std::string const &type);
};
- learnMateria로 탐색할 Materia를 배열에 저장한다.
- createMateria로 배열에 있는 Materia를 생성하여 반환한다.
- MateriaSource 또한
Materia *
복사가 이뤄질 때 이전의 배열에 있는 원소들을 적절히 delete한 후 복사가 이뤄져야 한다.
- 깊은 복사를 위해 Materia의 clone함수 이용.
에러
In file included from srcs/AMateria.cpp:13:
In file included from incs/AMateria.hpp:18:
incs/ICharacter.hpp:25:21: error: unknown type name 'AMateria'
virtual void equip(AMateria *m) = 0;
- AMateria.hpp 와 ICharacter가 서로의 헤더를 인클루드 하게 돼서 생기는 문제.
#ifndef ICHARACTER_CLASS_H
# define ICHARACTER_CLASS_H
# include "AMateria.hpp"
class AMateria;
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;
};
#endif
- ICharacter 클래스, AMateria 클래스 각각 선언 전에 서로의 class 선언해서 문제 해결