[42Seoul] 씨쁠쁠04

오젼·2022년 6월 9일
0

[42Seoul]

목록 보기
14/24

ex00

다형성(polymorphism)

  • https://modoocode.com/210
  • 하나의 메소드를 호출했음에도 여러가지 작업을 하는 것을 다형성이라고 한다.
  • 저번에 공부했던 virtual 키워드 연장선~

가상 함수

  • 동적 바인딩을 사용하고 싶은 함수에 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; // Returns the materia type

	virtual AMateria *clone() const = 0;
	virtual void use(ICharacter &target) { (void) target; }
};
  • Materia들의 기반 클래스.

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과 인벤토리 배열을 가짐.
  • 주의할 점
    1. 생성자에서 inventory 배열 목록 초기화. 각각 널포인터 연결해준다.
    2. 복사생성자, 대입연산자로 새로운 inventory목록 복사해 오는 경우 기존 inventory에 할당돼있던 AMateria 객체는 해제(delete)해줘야 한다.
    3. 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 선언해서 문제 해결

0개의 댓글