Hash Table을 구현하다가 제목과 같은 오류를 만났다.
아래와 같은 코드를 작성하다가 만났다. 아래 코드에서 "에러 발생 지점"이라고 표시한 부분을 잘 보자
template <typename Key, typename Value>
class CHashNode
{
template <typename Key, typename Value, int HASHSIZE>
friend class CHashTable;
template <typename Key, typename Value>
friend class CHashTableIterator;
private:
CHashNode()
{
}
~CHashNode()
{
// 에러 발생 지점
// 아래 코드 2줄 맨 앞에 typename을 붙여줘야 한다.
CList<CHashNode<Key, Value>*>::iterator iter = m_Chain.begin();
// 에러 발생 지점
CList<CHashNode<Key, Value>*>::iterator iterEnd = m_Chain.end();
for (; iter != iterEnd; ++iter)
{
delete *iter;
}
}
// 다른 구현한 멤버 함수, 멤버 변수들...
};
CList 클래스는 이전에 구현했던 클래스를 가져왔다.
template <typename T>
class CListIterator
{
template <typename T>
friend class CList;
public:
CListIterator() :
m_Node(nullptr)
{
}
~CListIterator()
{
}
private:
CListNode<T>* m_Node;
public:
// iterator끼리 서로 가지고 있는 노드가 같을 경우 같다고 판단한다.
bool operator == (const CListIterator<T>& iter) const
{
return m_Node == iter.m_Node;
}
bool operator != (const CListIterator<T>& iter) const
{
return m_Node != iter.m_Node;
}
bool operator == (const CListNode<T>* Node) const
{
return m_Node == Node;
}
bool operator != (const CListNode<T>* Node) const
{
return m_Node != Node;
}
void operator ++ ()
{
m_Node = m_Node->m_Next;
}
void operator ++ (int)
{
m_Node = m_Node->m_Next;
}
void operator -- ()
{
m_Node = m_Node->m_Prev;
}
void operator -- (int)
{
m_Node = m_Node->m_Prev;
}
T& operator * () const
{
return m_Node->m_Data;
}
};
template <typename T>
class CList
{
public:
CList()
{
m_Size = 0;
// Begin과 End노드를 생성하고 두 노드를 서로 연결한다.
m_Begin = new NODE;
m_End = new NODE;
m_Begin->m_Next = m_End;
m_End->m_Prev = m_Begin;
}
~CList()
{
// Begin노드는 이전노드가 nullptr이다.
// End노드는 다음노드가 nullptr이다.
PNODE DeleteNode = m_Begin;
while (DeleteNode)
{
PNODE Next = DeleteNode->m_Next;
delete DeleteNode;
DeleteNode = Next;
}
}
private:
typedef CListNode<T> NODE;
typedef CListNode<T>* PNODE;
public:
typedef CListIterator<T> iterator;
private:
// Begin과 End는 데이터를 저장하기 위한 노드는 아니다.
// 명시적으로 시작과 끝을 의미하는 노드로 사용하기 위해 할당해두고 사용한다.
// 실제 데이터를 저장하는 노드는 Begin과 End노드 사이에 위치하게 될것이다.
PNODE m_Begin;
PNODE m_End;
int m_Size;
iterator begin() const
{
iterator iter;
iter.m_Node = m_Begin->m_Next;
return iter;
}
iterator end() const
{
iterator iter;
iter.m_Node = m_End;
return iter;
}
void operator = (const CList<T>& list)
{
// 기존 정보를 제거한다.
clear();
m_Size = list.m_Size;
PNODE Node = list.m_Begin->m_Next;
while (Node != list.m_End)
{
push_back(Node->m_Data);
Node = Node->m_Next;
}
}
// 나머지 멤버 함수들은 생략...
};
CList<CHashNode<Key, Value>*>::iterator iter = m_Chain.begin();
이 코드에서 맨 앞에 typename을 붙여줘야 하는 이유는 컴파일러 입장에서 CList<CHashNode<Key, Value>*>::iterator 이것이 타입인지 아닌지 모르기 때문이다.
예를 들어서 아래 예시를 보자
template <class T>
void foo() {
T::iterator * iter;
...
}
class ContainsAType {
class iterator { ... }:
...
};
int main()
{
foo<ContainsAType>();
}
이렇게 foo를 호출하면서 T인자로 ContainsAType을 넘겨줄 때 foo() 안에서는 "ContainsAType안에 iterator라는 타입이 있군, 그래서 iter은 그 iterator 타입의 포인터구나" 문제는 이걸 ContainsAType을 넘겨줄 때가 되서야 안다는 것이지 컴파일러가 foo함수 안에서 T::iterator *iter라는 코드를 만났을 때는 이걸 모른다는 것이다.
컴파일러 입장에서는 이런 경우라고 생각할 수도 있다.
template <class T>
void foo() {
T::iterator * iter;
...
}
class ContainsAValue {
static int iterator;
};
int main()
{
foo<ContainsAType>();
}
이러면 컴파일러는 ContainsAValue안에 iterator변수와 iter의 곱하기로 인지할 수도 있다. 말하고자 하는 요지는 컴파일러는 저 T::iterator *iter라는 코드를 만났을 때는 저게 타입인지 아닌지 구별할 수 없다는 것이다.
따라서 템플릿인자에 의존적인 타입을 사용할때는 항상 typename을 붙여주자.
Reference
http://pages.cs.wisc.edu/~driscoll/typename.html#dependent
'공부 > 그 외' 카테고리의 다른 글
C2678 : 이항 '~': 왼쪽 피연산자로 '~' 형식을 사용하는 연산자가 없거나 허용되는 변환이 없습니다. (0) | 2021.09.02 |
---|---|
레지스터(Register) vs 레지스트리(Registry) (0) | 2021.08.18 |
꼬리 재귀(Tail recursion) (0) | 2021.08.04 |
오브젝트 풀(Object Pool) (0) | 2021.07.19 |
Visual Studio "const char *" 형식의 값을 사용하여 "char *" 형식의 엔터티를 초기화할 수 없습니다. (0) | 2021.07.16 |