#include <iostream>
using namespace std;
#include <vector>
#include <list>
#include <queue>
#include <map>
#include <unordered_map>
// dynamic_cast 까지 가기 싫다면? enum 을 써보기!
enum class ObjectType
{
Player,
Monster,
Projectile,
Env
};
class Object
{
public:
Object(ObjectType type) : _type(type) { }
Object(int id) : _id(id) { }
virtual ~Object() { }
/*
소멸자에 virtual을 붙이지 않으면 당장 크래시가 나는 것은 아니다.
그러나 치명적인 문제를 일으킬 수 있다.
객체화된 자식 클래스의 소멸자가 제대로 호출되지 못한다.
RTTI 가 없어서 호출이 안됨.
소멸자에서 추가적으로 중요한 작업(플레이어의 펫 정보 등)을 하고 있던
최악의 경우에는 메모리 누수 등의 문제가 일어날 수 있다.
*/
public:
ObjectType GetObjectType() { return _type; }
int _id;
ObjectType _type;
};
class Player : public Object
{
public:
Player() : Object(ObjectType::Player) { }
Player(int id) : _id(id) { }
// int _id;
};
class Monster : public Object
{
public:
Monster() : Object(ObjectType::Monster)() { }
int _id;
};
class Projectile : public Object
{
public:
Projectile() : Object(ObjectType::Projectile)() { }
};
class Env : public Object // 채집물
{
public:
Env() : Object(ObjectType::Env)() { }
};
class Field
{
public:
static Field* GetInstance()
{
static Field field;
return &field;
}
void Add(Object* player)
{
_objects.insert(make_pair(player->id, player));
}
void Remove(int id)
{
_objects.erase(id);
}
Object* Get(int id)
{
auto findIt = _objects.find(it);
if (findIt != _objects.end())
return findIt->second;
return nullptr;
}
// vector? list? map? hash_map? 이 중에서 무엇을 쓸지는 자신이 정해야 한다.
private:
unordered_map<int, Object*> _objects;
// unordered_map<int, Player*> _players;
// unordered_map<int, Monster*> _monsters;
// unordered_map<int, Projectile*> _projectiles;
// unordered_map<int, End*> _env;
//.. 이렇게 늘리거나, 상위 클래스를 만들어서 하나로 뭉치거나.
//.. 이렇게 늘렸을 때 점이라면 매번 스캔해야한다는 것.
}
int main()
{
Field::GetInstance()->Add(new Player(1));
// dynamic_cast 까지 가기 싫다면? enum 을 써보기!
Object* obj = Field::GetInstance()->Get(1);
if (obj && obj->GetObjectType() == ObjectType::Player)
{
Player* player = static_cast<Player>(obj);
}
// 캐스팅 4종 중에 어떤 캐스팅을 써서 얘가 player 라는 걸 확인할까?
// dynamic_cast 가 없으면 nullptr 로 반환하는 아이이다.
Player* player = dynamic_cast<Player*>(Field::GetInstance()->Get(1));
if (player)
{
// ...
}
}
동적 바인딩에 의해 실제 객체의 타입에 따라 적절한 draw() 함수가 호출
#include <iostream>
class Shape{
public:
virtual void draw()
{
std::cout << "Drawing a shape." << std::endl;
}
};
class Circle : public Shape {
public:
void draw() override
{
std::cout << "Drawing a circle." << std::endl;
}
};
class Square : public Shape {
public:
void draw() override
{
std::cout << "Drawing a square." << std::endl;
}
};
int main()
{
Shape* shape1 = new Circle();
Shape* shape2 = new Square();
shape1->draw(); // 동적 바인딩에 의해 Circle 클래스의 draw() 함수 호출
shape2->draw(); // 동적 바인딩에 의해 Square 클래스의 draw() 함수 호출
delete shape1;
delete shape2;
return 0;
}
다양한 동물 객체를 하나의 컨테이너로 관리하고 다형성을 활용
#include <iostream>
#include <vector>
class Animal {
public:
virtual void makeSound() {
std::cout << "Animal sound!" << std::endl;
}
};
class Dog : public Animal {
public:
void makeSound() override {
std::cout << "Woof!" << std::endl;
}
};
class Cat : public Animal {
public:
void makeSound() override {
std::cout << "Meow!" << std::endl;
}
};
int main()
{
std::vector<Animal*> animals;
animals.push_back(new Dog());
animals.push_back(new Cat());
for (const auto& animal : animals) {
animal -> makeSound(); // 동적 바인딩에 의해 각각의 동물 클래스의 makeSound() 함수 호출
}
for (auto* animal : animals) {
delete animal;
}
return 0;
}
메모리 누수를 방지하기 위해 가상 소멸자를 사용하는 기본적인 에시
#include <iostream>
class Base
{
public:
Base() {
std::cout << "Base constructor" << std::endl;
}
virtual ~Base() {
std::cout << "Base destructor" << std::endl;
}
};
class Derived : public Base
{
public:
Derived() {
std::cout << "Derived constructor" << std::endl;
}
~Derived() override {
std::cout << "Derived destructor" << std::endl;
}
};
int main()
{
Base* basePtr = new Derived(); //Derived 클래스의 객체를 Base 포인터로 가리키고 있다
delete basePtr;
/*
이때, Base 클래스의 가상 소멸자가 가상이므로,
Derived 클래스의 소멸자가 호출되면서 객체를 올바르게 소멸시킵니다.
이를 통해 객체가 올바르게 소멸되고 메모리 누수가 방지됩니다.
*/
return 0;
}
Run-Time Type Identification
실행시간에 객체의 실제 타입을 확인하는 기능을 말하며
C++의 다형성을 활용하기 위해 사용되고 주로 가상 함수와 연관되어 사용된다.
가상 함수는 부모 클래스의 포인터나 참조자를 사용하여 자식 클래스의 멤버 함수를 호출할 수 있는 기능을 제공한다. 이를 통해 객체의 다양한 타입을 가리키는 포인터를 사용할 수 있고, 실행 시간에 실제 객체의 타입을 판별하여 적절한 멤버 함수를 호출할 수 있게 됟나.
RTTI
는 가상 함수를 통해 객체의 다양성을 활용하는 상황에서 객체의 실제 타입을 확인하기 위해 사용된다. dynamic_cast
연산자를 사용하여 객체의 타입을 다른 타입으로 변환하고, 이를 통해 객체가 특정 타입인지 여부를 확인할 수 있다. 이를 통해 객체가 특정 클래스의 인스턴스인지 확인하거나, 상속 계층 구조에서 다른 타입으로 다운캐스팅을 시도할 수 있다.