[MFC] CMap class 사용법 예제

반응형

MFC의 CMap 클래스는 키와 값의 쌍으로 이루어진 맵을 구현하는 클래스입니다. C++ 표준라이브러리의 map 클래스와 유사하지만 MFC에 특화된 기능을 추가로 제공합니다. CMap 클래스는 MFC에 종속적입니다.

이번 포스팅에서는 CMap 클래스 초기화, 삽입, 탐색, 삭제 등을 예제 코드로 알아보겠습니다.

 

1. CMap 클래스 사용사례

배열이나 리스트로 자료를 관리해야 할 때 특정 값이 어느 인덱스에 있는지 찾으려면 탐색을 해야 하는데 최악의 경우 N-1까지 탐색을 해야 합니다. 이때 찾으려는 값을 KEY, 인덱스를 VALUE로 CMap에 저장해 두면 deterministic 하게 탐색 가능합니다.

 

2. CMap 클래스 설명

CMap을 사용하려면 CMap 인스턴스를 생성해야 하는데 공식 MSDN 구문은 다음과 같습니다.

template<class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>class CMap : public CObject

 

MSDN 설명으로는 잘 모르겠으니 실제 작성 코드를 보겠습니다.

KEY, VALUE가 필요한데 2개씩 중복이 되어 있는 것을 확인할 수 있습니다.

KEY-원형, KEY-레퍼런스타입파라미터, VALUE-원형, VALUE_레퍼런스타입파라미터라고 보시면 됩니다.

문자열은 MFC에서 LPCTSTR으로 참조하니까 CString의 경우에도 LPCTSTR으로 해주시면 되고

문자열이 아닌 다른 타입들은 원형과 레퍼런스 타입을 똑같이 기입해 주시면 됩니다.

typedef CMap<CString, LPCTSTR, int, int> TMAP_DS_STOCK_INDEX;
typedef CMap<CString, LPCTSTR, TST__DS_BALANCE_OCCURS*, TST__DS_BALANCE_OCCURS*> TMAP_DS_BALANCE_OCCURS;   /* KEY: LPCTSTR SH CODE. */
typedef CMap<long, long, TST__DS_UNEXECUTED_ORDER*, TST__DS_UNEXECUTED_ORDER*> TMAP_DS_UNEXECUTED_ORDER;   /* KEY: LONG OrderNumber */
typedef CMap<CString, LPCTSTR, PTST_RIL_SESSION_NODE, PTST_RIL_SESSION_NODE> TMAP_PTST_RIL_SESSION_NODE_BY_tchNoyeNetId;
typedef CMap<SOCKET, SOCKET, PTST_RIL_SESSION_NODE, PTST_RIL_SESSION_NODE> TMAP_PTST_RIL_SESSION_NODE_BY_SOCKET;
typedef CMap<PTST_RIL_SESSION_NODE, PTST_RIL_SESSION_NODE, SOCKET, SOCKET> TMAP_PTST_RIL_SESSION_NODE_POOL;
typedef CMap<UINT, UINT, PTST_RIL_SESSION_NODE, PTST_RIL_SESSION_NODE> TMAP_PTST_RIL_SESSION_NODE_ALLOC_POOL;

 

MSDN에 나와 있는 공용 메서드 목록은 다음과 같습니다.

  • CMap::GetCount 이 맵의 요소 수를 반환합니다.
  • CMap::GetHashTableSize 해시 테이블의 요소 수를 반환합니다.
  • CMap::GetNextAssoc 반복할 다음 요소를 가져옵니다.
  • CMap::GetSize 이 맵의 요소 수를 반환합니다.
  • CMap::GetStartPosition 첫 번째 요소의 위치를 반환합니다.
  • CMap::InitHashTable 해시 테이블을 초기화하고 해당 크기를 지정합니다.
  • CMap::IsEmpty 빈 맵 조건(요소 없음)에 대한 테스트입니다.
  • CMap::Lookup 지정된 키에 매핑된 값을 조회합니다.
  • CMap::PGetFirstAssoc 첫 번째 요소에 대한 포인터를 반환합니다.
  • CMap::PGetNextAssoc 반복할 다음 요소에 대한 포인터를 가져옵니다.
  • CMap::PLookup 값이 지정된 값과 일치하는 키에 대한 포인터를 반환합니다.
  • CMap::RemoveAll 이 맵에서 모든 요소를 제거합니다.
  • CMap::RemoveKey 키로 지정된 요소를 제거합니다.
  • CMap::SetAt 맵에 요소를 삽입합니다. 는 일치하는 키가 있으면 기존 요소를 대체합니다.

3. CMap 클래스 사용 예제

3.1. 메모리 풀 구현에 CMap 클래스 사용

TST_RIL_SESSION_NODE 타입을 배열로 여러개 만들어두고 메모리 풀로 관리하려고 합니다.

점유되지 않은 슬롯을 찾기 위해 탐색을 해야 하는데 이 때 CMap이 유용하게 사용됩니다.

TST_RIL_SESSION_NODE_POOL에 mapFreeNodesPool, mapAllocNodesPool을 두었습니다.

그리고 구조체 생성자에서 두 맵을 .InitHashTable() 로 크기를 할당합니다. InitHashTable은 호출해도 되고 안 해도 됩니다.

어차피 동적으로 사용 가능합니다. 그 다음 for() 문에서 "mapFreeNodesPool[&(astNodes[i])] = i;" 코드가 초기화의 핵심입니다. PTST_RIL_SESSION_NODE 를 map 인덱스로 하여 배열 인덱스를 저장하고 있습니다.

"mapFreeNodesPool.SetAt(&(astNodes[i]), i);"으로 사용하는 것과 동일합니다.

/* 메모리 객체 구조체 타입 */
typedef struct _ril_session_node : OVERLAPPED {
	const WORD __wMemPoolIndex;
	const BOOL __boDynamicallyAllocated;
	SRWLOCK srwlock;

	SOCKET _sock;
	char _buff[RIL_SESSION_NODE_BUF_LEN];
	DWORD dwUsedLen;	// used length of _buff. purpose: resuming truncated packets

	_ril_session_node():__wMemPoolIndex(0), __boDynamicallyAllocated(FALSE) {
		ZeroMemory(this, sizeof(_ril_session_node));
		_sock = INVALID_SOCKET;
		InitializeSRWLock(&srwlock);
	}

} TST_RIL_SESSION_NODE, * PTST_RIL_SESSION_NODE;

/* PTST_RIL_SESSION_NODE를 KEY로 갖는 CMap 클래스 타입 */
typedef CMap<PTST_RIL_SESSION_NODE, PTST_RIL_SESSION_NODE, UINT, UINT> TMAP_PTST_RIL_SESSION_NODE_ALLOC_POOL;

/* PTST_RIL_SESSION_NODE 를 관리할 메모리풀 구조체 타입 */
typedef struct _ril_session_node_pool {
	SRWLOCK srwlock;
	WORD wFreeNodesCnt;
	TMAP_PTST_RIL_SESSION_NODE_ALLOC_POOL mapFreeNodesPool;	/* Free Nodes */
	TMAP_PTST_RIL_SESSION_NODE_ALLOC_POOL mapAllocNodesPool;	/* Allocated Nodes */
	TST_RIL_SESSION_NODE astNodes[MAX_CAPACITY_COUNTOF_SESSION_NODE];
	_ril_session_node_pool() {
		wFreeNodesCnt = sizeof(astNodes) / sizeof(TST_RIL_SESSION_NODE);
		mapFreeNodesPool.InitHashTable((sizeof(astNodes) / sizeof(TST_RIL_SESSION_NODE)));
		mapAllocNodesPool.InitHashTable((sizeof(astNodes) / sizeof(TST_RIL_SESSION_NODE)));

		for (int i = 0; i < (sizeof(astNodes) / sizeof(TST_RIL_SESSION_NODE)); i++) {
			(WORD)(astNodes[i].__wMemPoolIndex) = (WORD)i;
			mapFreeNodesPool[&(astNodes[i])] = i;
			/* Remove Usage: mapFreeNodesPool.RemoveKey[i]; */
		}
		InitializeSRWLock(&srwlock);
	}
} TST_RIL_SESSION_NODE_POOL, * PTST_RIL_SESSION_NODE_POOL;

이제 위에서 정의한 메모리 풀을 사용하기 위해 메모리 풀 인스턴스 생성 후 할당 함수 코드를 작성해 보겠습니다.

"new TST_RIL_SESSION_NODE" 호출 대신, "pAlloc_PTST_RIL_SESSION_NODE()"호출로 메모리풀에서 할당받을 수 있습니다. 

_m_stPrsnAllocPool.IsEmpty() 호출 대신 Diagnostics 용도의 wFreeNodesCnt를 체크하여 "_m_stPrsnAllocPool.wFreeNodesCnt <= 0" 코드로 Empty여부를 판단하는데 여러분 자유입니다.

할당은 비어있는 아무 슬롯을 최대한 빨리 찾아 주면 되기 때문에 "PGetFirstAssoc()" 메서드를 사용합니다. 

"CPair"가 핵심입니다. CPair* p에는 KEY와 VALUE가 들어있습니다.

예제코드에서의 p->key는 PTST_RIL_SESSION_NODE입니다. p->key->를 통해 PTST_RIL_SESSION_NODE의 각 필드에도 접근할 수 있습니다.

/* 메모리 풀 인스턴스 생성 */
TST_RIL_SESSION_NODE_POOL _m_stPrsnAllocPool;

PTST_RIL_SESSION_NODE CRilSession::pAlloc_PTST_RIL_SESSION_NODE(void)
{
	CAutoScopeSRWLOCKExclusive lock(&(_m_stPrsnAllocPool.srwlock));

	if (_m_stPrsnAllocPool.wFreeNodesCnt <= 0) {
		return nullptr;
	}

	TMAP_PTST_RIL_SESSION_NODE_ALLOC_POOL::CPair* p = _m_stPrsnAllocPool.mapFreeNodesPool.PGetFirstAssoc();
	_m_stPrsnAllocPool.mapFreeNodesPool.RemoveKey(p->key);
	_m_stPrsnAllocPool.mapAllocNodesPool.SetAt(p->key, p->value);
	_m_stPrsnAllocPool.wFreeNodesCnt--;

	return (p->key);
}

void CRilSession::vFree_PTST_RIL_SESSION_NODE(PTST_RIL_SESSION_NODE pKey)
{
	if (nullptr == pKey) {
		return;
	}

	CAutoScopeSRWLOCKExclusive lock(&(_m_stPrsnAllocPool.srwlock));

	BOOL boRet = _m_stPrsnAllocPool.mapAllocNodesPool.RemoveKey(pKey);
	
	if (FALSE == boRet) {
		if (TRUE == pKey->__boDynamicallyAllocated) {	// allocated dynamically by "new TST_RIL_SESSION_NODE"
			delete pKey;
		}
		return;
	}

	pKey->vResetFiledData();
	pKey->_sock = INVALID_SOCKET;

	_m_stPrsnAllocPool.mapAllocNodesPool.SetAt(pKey, pKey->__wMemPoolIndex);
	_m_stPrsnAllocPool.wFreeNodesCnt++;

	return;
}

 

3.2. Key값에 대응하는 Value 얻기

Lookup메서드를 사용합니다. 파라미터로 넘기는 Key, Value 둘 다 레퍼런스 타입임에 유의하세요.

포인터를 넘기지 않습니다. Lookup에 성공하면 TRUE를 반환하므로 이때 Value 값이 유효합니다.

int CDataStore::iGet_TST_DS_STOCK_MASTER_expCodeIndex(LPCTSTR expCode)
{
	int iIndex;
	BOOL boRet;

	AcquireSRWLockShared(&(_m_stDS_STOCK_INDEX.srwlock));
	boRet = _m_stDS_STOCK_INDEX.mapIndexEXP.Lookup(expCode, iIndex);
	ReleaseSRWLockShared(&(_m_stDS_STOCK_INDEX.srwlock));

	if (TRUE == boRet)
		return iIndex;
	else
		return -1;
}

 

3.3. CMap 훑어보기

CMap 클래스 내부의 전체 키와 값을 출력하려면 CMap 클래스의 GetStartPosition() 메서드와 GetNextAssoc() 메서드를 사용하여 가능합니다.

POSITION pos;
PTST__DS_BALANCE_OCCURS p = nullptr;
posOccurs = pBalanceObj->pMapOccurs->GetStartPosition();
while (pos) {
			pBalanceObj->pMapOccurs->GetNextAssoc(posOccurs, cstrTemp, p);

			/* Do Something~~ */
 }

 

이상으로 MFC에서 CMap 클래스를 사용하는 법을 알아보았습니다.

반응형