이전 포스트에서 CSBLOCK 매크로를 사용해서 좀 더 간편하게 크리티컬 섹션을 정의할 수 있도록 했었다. 이에 조금 덧붙여서, 어떤 데이터형이든지, 그에 대응하는 크리티컬 섹션 변수를 만들어 줄 수 있도록 하고 싶었다. 이 작업도중 Variadic macro to count number of arguments을 읽게 되었고, 옳타구나 하고 여러 개의 자료형을 매크로의 파라미터로 사용할 수 있도록 수정했다.
다음은 이 아이디어를 구현한 코드이다.
locker 클래스는 주소값에 대응하는 값(이 코드에서는 int형 key를 사용했지만, 윈도우의 크리티컬 섹션을 위해서는 CRITICAL_SECTION 타입의 변수가 될 것이다)을 등록/삭제/검색 한다.
class locker { public: locker() : _key(0) {} ~locker() { map<unsigned int, int>::iterator itr, itrend; itr = _users.begin(); itrend = _users.end(); for (; itr != itrend; ++itr) { unregister_addr((void*)itr->first); } } int register_addr(void* address) { map<unsigned int, int>::iterator itr = _users.find((unsigned int)address); if (itr != _users.end()) return itr->second; cout << "register: " << hex << (unsigned int)address << endl; _users[(unsigned int)address] = ++_key; return _key - 1; } int unregister_addr(void* address) { int key = 0; map<unsigned int, int>::iterator itr = _users.find((unsigned int)address); if (itr == _users.end()) return key; key = itr->second; cout << "unregister: " << hex << (unsigned int)address << endl; _users.erase((unsigned int)address); return key; } int key(void* addr) { map<unsigned int, int>::iterator itr = _users.find((unsigned int)addr); if (itr == _users.end()) return 0; return itr->second; } private: map<unsigned int, int> _users; int _key; };// class locker
이 클래스에는 약간의 문제가 있는데, _users, _key 변수 역시 크리티컬 섹션이라는 점이다. 따라서 이에 대한 처리도 필요하다. 또한, 주소값에 대한 key를 동적으로 생성하는데 대한 성능상의 문제도 존재한다. 그렇지만, 어디까지나 이는 아이디어를 구현하기 위한 과정으로 판단해서 일단 패스. :)
scope_lock_args 클래스는 CSBLOCK 매크로가 여러 개의 인자를 받을 수 있도록 해준다.
class scope_lock_args { friend class scope_lock; public: scope_lock_args(int count, ...) { va_list addresses; va_start(addresses, count); for (int i = 0; i < count; ++i) _addresses.insert(va_arg(addresses, void*)); va_end(addresses); } typedef std::set<void*> data_type; private: std::set<void*> _addresses; };// class scope_lock_args
scope_lock 클래스의 생성자와 소멸자에서 크리티컬 섹션의 시작과 종료를 알린다. 각 데이터는 scoke_lock_args 클래스가 주소에 따라 정렬해서 관리하기 때문에 항상 같은 순서로 락을 걸고 풀 수 있다.
class scope_lock { public: scope_lock(scope_lock_args arg) : _arg(arg) { scope_lock_args::data_type::iterator itr, itrend; itr = _arg._addresses.begin(); itrend = _arg._addresses.end(); for (; itr != itrend; ++itr) { _locker.register_addr(*itr); cout << "scope_lock::scope_lock " << hex << *itr << endl; // EnterCriticalSection } } ~scope_lock() { scope_lock_args::data_type::reverse_iterator itr, itrend; itr = _arg._addresses.rbegin(); itrend = _arg._addresses.rend(); for (; itr != itrend; ++itr) { cout << "scope_lock::~scope_lock " << hex << *itr << endl; // LeaveCritialSection } } operator bool() const { return true; } private: static locker _locker; scope_lock_args _arg; };// class scope_lock locker scope_lock::_locker;
하일라이트. CSBLOCK는 최소 하나부터 최대 세 개까지의 데이터를 인자로 받을 수 있다. 물론, 필요하면 늘릴 수 있다.
#define CSBLOCK(...) CSBLOCK_IMPL(__VA_ARGS__, 3, 2, 1) #define CSBLOCK_IMPL(_1, _2, _3, N, ...) if (scope_lock __sl = scope_lock_args(N, _1, _2, _3))
이제는 CSBLOCK 매크로에 여러 개의 인자를 넣을 수 있고, 그 순서에 상관없이 항상 같은 순서로 락을 사용한다.
#include <iostream> #include <map> #include <set> #include <cstdarg> using namespace std; int main() { int k = 0; int l = 0; CSBLOCK(&k) { cout << "<1>" << endl; CSBLOCK(&l, &k) // 순서 상관 없음 { cout << "<2>" << endl; } CSBLOCK(&k) { cout << "<3>" << endl; } CSBLOCK(&k, &l) // 순서 상관 없음 { cout << "<4>" << endl; } } return 0; }
성능상의 문제로 실제로 사용하기에는 많은 개선이 필요하겠지만, 꽤 매력적이지 않나? ㅋㅋㅋ
C++은 확실히, 이런 것들이 재미있는 것 같다.