[C++] STL 복습해보자

seunghyun·2023년 6월 28일
0

코드

#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) 
    {
    	// ...
    }
}


가상함수 활용예시

1

동적 바인딩에 의해 실제 객체의 타입에 따라 적절한 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;
}

2

다양한 동물 객체를 하나의 컨테이너로 관리하고 다형성을 활용

#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;
}

3

메모리 누수를 방지하기 위해 가상 소멸자를 사용하는 기본적인 에시

#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;
}


보충

RTTI

Run-Time Type Identification
실행시간에 객체의 실제 타입을 확인하는 기능을 말하며
C++의 다형성을 활용하기 위해 사용되고 주로 가상 함수와 연관되어 사용된다.

가상 함수는 부모 클래스의 포인터나 참조자를 사용하여 자식 클래스의 멤버 함수를 호출할 수 있는 기능을 제공한다. 이를 통해 객체의 다양한 타입을 가리키는 포인터를 사용할 수 있고, 실행 시간에 실제 객체의 타입을 판별하여 적절한 멤버 함수를 호출할 수 있게 됟나.

RTTI는 가상 함수를 통해 객체의 다양성을 활용하는 상황에서 객체의 실제 타입을 확인하기 위해 사용된다. dynamic_cast연산자를 사용하여 객체의 타입을 다른 타입으로 변환하고, 이를 통해 객체가 특정 타입인지 여부를 확인할 수 있다. 이를 통해 객체가 특정 클래스의 인스턴스인지 확인하거나, 상속 계층 구조에서 다른 타입으로 다운캐스팅을 시도할 수 있다.

0개의 댓글