#define Property(TYPE, X) class { private:TYPE value; public:TYPE& operator=(const TYPE& rhs) { return value = rhs; }operator TYPE() { return value; }TYPE& Value(){return value;}} X;
struct Foo {
int a;
};
int main()
{
Property(int, a);
Property(Foo, b);
Foo d;
d.a = 3;
b = d;
Foo c = b;
cout << b.Value().a;
}
Admin도 그렇고 아직 만들지는 않았지만 다른 엔진들도 인스턴스가 1개만 생성됨을 보장해주어야 했다. 그래서 싱글톤 패턴을 적용하기 위해서 일종의 인터페이스같은 클래스를 만들어보기로 했다.
(인터페이스라고 말하긴 했지만 순수가상함수가 없어서 그냥 대충 이름 붙인거라 보면 됨.)
#pragma once
#include <memory>
/*
싱글톤 인터페이스.
사용법::상속하면 되는데 클래스의 기본 생성자를 private에 배치후 friend로 해줘야 함..
*/
template <typename T>
class ISingleton
{
static std::weak_ptr<T> weakedptr;
public:
static std::shared_ptr<T> Get()
{
auto instance = weakedptr.lock();
if (!instance)
{
instance.reset(new T());
weakedptr = instance;
}
return instance;
}
protected:
ISingleton() {}
virtual ~ISingleton() {}
};
template <typename T> std::weak_ptr<T> ISingleton<T>::weakedptr;
class Admin :public ISingleton<Admin>
{
friend ISingleton<Admin>;
private:
class Windows {
using WindowPtr = std::shared_ptr<Window>;
using WindowContainer = std::vector<WindowPtr>;
WindowContainer windows;
public:
void Add(Window * window);
} windows;
protected:
Admin();
public:
void AddWindow(Window* window);
static LRESULT CALLBACK MessageHandler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
};
이런 식으로 만들어서 Admin에 적용해보려고 하니 한가지 문제점이 있었다. 주석에도 써놨지만 Get()함수에서 T의 생성자를 호출하려면 대상 클래스에서 friend 클래스로 이 싱글톤 인터페이스 클래스를 지정해주어야 private에 있는 생성자를 Singleton에서 호출할 수 있게 된다.
더 개선할 방법이 없을까..
요는 그거다. 이 싱글톤 클래스에서 대상 클래스의 생성자를 대리 호출 가능하지만 외부에서는 대상 클래스의 생성자를 호출할 수 없도록 한다.
음... 검색을 조금 해봤는데 싱글톤 패턴은 가능한 지양하는 게 좋다고 한다. 그러니 다른 엔진의 경우는 Admin에 Get함수를 추가해서 Admin 하나만 전역에 두고 다른 엔진을 서로 불러올 수 있도록 만들면 될 것 같다. 그래서 싱글톤은 일단 패스.
이제 Admin 클래스에 new Window(~~)를 인수로 넣어서 출력할 수 있도록 했고, shared_ptr로 만들어서 자동으로 지워질 수 있도록 처리했다.
다음으로 할 일은 해당 윈도우에 기본적으로 등록했던 WndProc 함수 대신 다른 함수를 집어넣고, 그 함수에서 Input 처리를 할 수 있도록 하는 일이다. 물론 아직 Input을 구현한다고 해서 뭔가 할 수 있진 않지만 입력 처리는 가장 기본적인 거니까 빨리 만들어 두는 게 좋을 것 같다.
Input으로는 키보드와 마우스 2가지를 일단 생각할 수 있을 것 같고, 키보드부터 먼저 해보기로 했다.
입력처리는 메세지 핸들러 함수에서 WM_KEYDOWN 등 메세지의 유형에 따라 wParam과 lParam을 해석을 해서 어떤 키가 눌렸는지를 알아내는 방식이다. 마우스도 마찬가지.
한창 하던 중 Getter와 Setter를 일일히 써준다는 게 너무 짜증이 났다. 그래서 C++에서도 C# 스타일의 프로퍼티를 사용할 수 없을까 고민해보았다.
일단 연산자 오버로딩으로 =를 사용할 수 있다는 건 알고 있었지만
하지만 setter는 잘 동작하는데 반해, getter는 내가 get/set 구현을 위해 만든 템플릿과 원래 값이 서로 다른 클래스이기 때문에 동작하지 않았다.
class Something {};
class Foo
{
public:
Property<Something> something;
};
template <typename T>
class Property {
public:
virtual ~Property() {} //C++11: use override and =default;
virtual T& operator= (const T& f) { return value = f; }
virtual const T& operator() () const { return value; }
virtual explicit operator const T& () const { return value; }
virtual T* operator->() { return &value; }
protected:
T value;
};
int main()
{
Foo foo;
Something s;
foo.something = s;
s = foo.something; //둘다 프로퍼티 클래스가 아니여서 성립을 못함...
}
결국 현실과 타협하는 수 밖에 없다. 방법은 2가지.
1. Getter(), Setter()를 포함하는 클래스를 제작해서 내가 일일히 Get(), Set() 함수를 쓰지 않아도 되게 한다.
2. 매크로를 이용해서 무명 클래스를 만든다.
음, 일단 매크로로 구현을 해보고 결정하자.
#define Property(TYPE, X) class { private:TYPE value; public:TYPE& operator=(const TYPE& rhs) { return value = rhs; }operator TYPE() { return value; }} X;
struct Foo {
int a;
};
int main()
{
Property(int, a);
Property(Foo, b);
Foo d;
d.a = 3;
b = d;
Foo c = b;
cout << c.a;
}
이렇게 하면 할당 연산도 되고 대입 연산도 되긴 하는데, 구조체 멤버에 접근하려면 결국 또 value를 리턴하는 함수를 만들 수 밖에 없다..
#define Property(TYPE, X) class { private:TYPE value; public:TYPE& operator=(const TYPE& rhs) { return value = rhs; }operator TYPE() { return value; }TYPE& Value(){return value;}} X;
struct Foo {
int a;
};
int main()
{
Property(int, a);
Property(Foo, b);
Foo d;
d.a = 3;
b = d;
Foo c = b;
cout << b.Value().a;
}
이런 식으로 만들 수 있다.
여기서 끝나면 Gettter Setter 구현에 의미가 없다. 내가 원하는 함수를 집어넣을 수 있게 해줘야 하지 않을까?