Singleton

싱글턴은 프로그램 전체에서 하나의 인스턴스만 존재하도록 보장하는 패턴이다. C++에서는 Meyer’s Singleton이 대표적이며, 정적 지역 변수를 활용해 지연 초기화와 스레드 안전성을 동시에 확보할 수 있다.

  class Cursor {
private:
    Cursor() {}
    Cursor(const Cursor&) = delete;
    Cursor& operator=(const Cursor&) = delete;

public:
    static Cursor& get_instance() {
        static Cursor instance;
        return instance;
    }
};

// 스레드에서 사용 예시
std::thread t1(&Cursor::get_instance);
std::thread t2(&Cursor::get_instance);
  

C++11 이전에는 Double-Checked Locking Pattern(DCLP)을 썼지만, 컴파일러 최적화로 인한 문제가 있었고, C++11 이후 해결되었다.

Factory

팩토리 패턴은 객체 생성을 전담하는 클래스를 별도로 분리하는 패턴이다. 생성 로직을 외부로 분리해 코드의 유연성과 확장성을 높여준다.

목적

  • 새로운 타입이 추가돼도 클라이언트 코드를 수정하지 않도록 하기 위해
  class Shape
{
public:
	virtual void draw() = 0;
	virtual ~Shape() {}
};

class ShapeFactory
{
 	MAKE_SINGLETON(ShapeFactory)
	using F = Shape*(*)();
	std::map<int, F> create_map;
public:
	void register_shape(int key, F create_function)
	{
		create_map[key] = create_function;
	}

	Shape* create(int type)
	{
		Shape* p = nullptr;
		if (create_map[type] != nullptr)
		{
			p = create_map[type](); //
		}
		return p;
	}
};

class RegisterFactory
{
public:
	RegisterFactory(int type, Shape* (*f)())
	{
		ShapeFactory::get_instance().register_shape(type, f);
	}
};

#define REGISTER
(classname) \
 static Shape* create() { return new classname; } \
 static RegisterFactory rf;

#define REGISTER_IMPL(type, classname) \
 RegisterFactory classname::rf(type, &classname::create);

class Rect : public Shape
{
public:
	void draw() override { std::cout << "draw Rect" << std::endl; }
	REGISTER(Rect)
};
REGISTER_IMPL(1, Rect)

class Circle : public Shape
{
public:
	void draw() override { std::cout << "draw Circle" << std::endl;}
 	REGISTER(Circle)
};
REGISTER_IMPL(2, Circle)

int main()
{
	std::vector<Shape*> v;
	ShapeFactory& factory = ShapeFactory::get_instance();
	while (1)
	{
		int cmd;
		std::cin >> cmd;
		if (cmd > 0 && cmd < 8)
		{
			Shape* s = factory.create(cmd);
			if ( s )
				v.push_back(s);
		}
		else if (cmd == 9)
		{
			for (auto s : v)
				s->draw();
		}
	}
}
  

이 구조는 추상 팩토리와도 유사하지만, 간단하게 map을 활용한 방식이다.

Prototype

위 예제는 클래스 자체를 등록했는데 자주 사용하는 객체를 등록하고, 복사하여 생성

Abstract

사용자가 옵션을 무엇을 줬냐에 따라 공장 자체를 바꿔서 사용 RichControlFactory ↔ SimpleControlFactory

Flyweight

Flyweight 패턴은 같은 데이터를 공유해서 메모리 사용을 최소화하는 방식이다. 동일한 속성을 가진 객체는 하나만 만들고, 필요할 때 참조로 재사용한다.

구조 예시: Image Factory

  class ImageFactory {
    std::map<std::string, Image*> image_map;
public:
    Image* create(const std::string& url) {
        auto ret = image_map.find(url);
        if (ret == image_map.end()) {
            Image* img = new Image(url);
            image_map[url] = img;
        }
        return image_map[url];
    }
};
  

구조 예시 2: Word 프로세서에서 문자 공유

  class Font { /* ... */ };
class Char {
    char value;
    Font* font; // 보통 같은 폰트는 공유된다
};