C++에서 다양한 컨테이너에 접근하기 위한 제네릭 방법 찾기
현대 C++에서 개발자들이 자주 직면하는 하나의 공통된 문제는 서로 다른 컨테이너 유형을 균일하게 순회(iterate)하는 것입니다. 표준 std::vector
, std::list
, 그리고 사용자 정의 목록이 있는 여러 개의 컨테이너를 상상해보십시오. 각 컨테이너는 고유한 이터레이터 유형을 가지기 때문에 깔끔하고 유지보수하기 쉬운 코드를 작성하는 것이 어려울 수 있습니다. 이 블로그 포스트에서는 이 문제를 탐구하고 이러한 컨테이너에 일반적으로 접근할 수 있는 우아한 솔루션을 제시하겠습니다.
문제
이러한 다양한 컨테이너에서 항목에 접근하려고 할 때, 빠르게 문제에 직면하게 됩니다. Foo
클래스에서 std::vector<int>
, std::list<int>
, 그리고 사용자 정의 목록과 상호작용할 수 있는 단일 이터레이터가 필요합니다. 그러나 이러한 모든 컨테이너가 서로 다른 이터레이터 유형을 반환하기 때문에, 하나의 이터레이터에 할당하려고 하면 유형 충돌이 발생합니다.
당신이 직면한 도전의 간단한 버전은 다음과 같습니다:
class Foo {
public:
Foo() {
std::list<int> x;
std::vector<int> y;
custom_list<int> z;
iter = x.begin(); // 또는
iter = y.begin(); // 또는
iter = z.begin(); // 문제: 서로 다른 이터레이터 유형
};
private:
std::iterator<int> iter; // 이 코드가 작동하지 않음
};
솔루션
코드를 우아하고 관리 가능하게 유지하면서 이 문제를 해결하기 위해, 우리는 **타입 지우기(type erasure)**라는 개념을 활용할 수 있습니다. 이 기술은 다양한 컨테이너 유형에서 항목에 균일하게 접근할 수 있는 제네릭 인터페이스를 생성할 수 있게 해줍니다.
1단계: 기본 이터레이터 클래스 정의
모든 이터레이터가 상속할 기본 인터페이스를 정의하여 다룰 것입니다. 이는 다양한 이터레이터 유형과 상호작용하는 공통한 방법을 제공합니다.
class IIterator {
public:
virtual ~IIterator() = default;
virtual int& operator*() = 0; // 역참조 연산자
virtual IIterator& operator++() = 0; // 증가 연산자
virtual bool operator!=(const IIterator& other) const = 0; // 비교 연산자
};
2단계: 각 컨테이너 유형에 대한 구현
다음으로, 다양한 컨테이너 유형에 대해 이 인터페이스를 구현할 것입니다.
std::vector에 대한 구현
class VectorIterator : public IIterator {
std::vector<int>::iterator it;
public:
VectorIterator(std::vector<int>::iterator iterator) : it(iterator) {}
int& operator*() override { return *it; }
IIterator& operator++() override { ++it; return *this; }
bool operator!=(const IIterator& other) const override {
return this->it != dynamic_cast<const VectorIterator&>(other).it;
}
};
std::list에 대한 구현
class ListIterator : public IIterator {
std::list<int>::iterator it;
public:
ListIterator(std::list<int>::iterator iterator) : it(iterator) {}
int& operator*() override { return *it; }
IIterator& operator++() override { ++it; return *this; }
bool operator!=(const IIterator& other) const override {
return this->it != dynamic_cast<const ListIterator&>(other).it;
}
};
사용자 정의 목록에 대한 구현
사용자 정의 목록이 이터레이터를 정의하고 있다고 가정할 때, 아래와 같이 유사하게 구현할 수 있습니다:
class CustomListIterator : public IIterator {
custom_list<int>::iterator it;
public:
CustomListIterator(custom_list<int>::iterator iterator) : it(iterator) {}
int& operator*() override { return *it; }
IIterator& operator++() override { ++it; return *this; }
bool operator!=(const IIterator& other) const override {
return this->it != dynamic_cast<const CustomListIterator&>(other).it;
}
};
3단계: Foo 클래스에서 이터레이터 사용하기
이제 Foo
클래스에서 IIterator
기본 클래스에 대한 단일 유형의 포인터를 유지하여 어떤 컨테이너 유형도 처리할 수 있습니다.
class Foo {
public:
Foo() {
std::list<int> x;
std::vector<int> y;
custom_list<int> z;
IIterator* iter;
// 각각의 컨테이너 인스턴스가 있다고 가정합니다.
iter = new VectorIterator(y.begin());
// 또는
iter = new ListIterator(x.begin());
// 또는
iter = new CustomListIterator(z.begin());
// 이제 iter를 균일한 이터레이터로 사용할 수 있습니다
};
};
결론
공통 이터레이터 인터페이스를 통한 타입 지우기를 활용함으로써, 우리는 C++에서 다양한 컨테이너 유형을 조작하는 우아한 방법을 제공하였습니다. 이는 코드의 깨끗함과 유지 보수성을 높이면서 여러 이터레이터 유형을 관리하는 번거로움을 피하는 데 도움을 줍니다.
이 주제에 대한 추가 탐색을 원하신다면, 다음의 유익한 기사를 살펴보시기 바랍니다:
이 솔루션은 C++ 기술을 향상시키는 데에만 그치지 않고, 다양한 컨테이너 유형을 매끄럽게 다룰 수 있는 다재다능하고 재사용 가능한 코드 구조를 만드는 데 도움을 줍니다.