[2026] C++ 컨테이너 어댑터 | stack·queue·priority_queue 완벽 가이드

[2026] C++ 컨테이너 어댑터 | stack·queue·priority_queue 완벽 가이드

이 글의 핵심

C++ 컨테이너 어댑터 stack (LIFO), queue (FIFO), priority_queue (힙)의 사용법과 실무 활용. DFS·BFS·다익스트라 알고리즘 구현, 괄호 검증, 작업 스케줄링 예제를 다룹니다.

들어가며

C++의 컨테이너 어댑터기존 컨테이너를 특정 인터페이스로 감싼 것입니다. stack (LIFO), queue (FIFO), priority_queue (힙) 세 가지가 있습니다. 비유로 말씀드리면, stack접시 쌓기 (마지막에 올린 것을 먼저 내림), queue줄 서기 (먼저 온 사람이 먼저 나감), priority_queue응급실 (우선순위가 높은 사람이 먼저 진료)에 가깝습니다.

이 글을 읽으면

  • stack, queue, priority_queue의 사용법을 이해합니다
  • 각 어댑터의 시간 복잡도를 파악합니다
  • DFS, BFS, 다익스트라 알고리즘을 구현합니다
  • 실무 활용 패턴과 주의사항을 확인합니다

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

목차

  1. 컨테이너 어댑터 개요
  2. 실전 구현
  3. 고급 활용
  4. 성능 비교
  5. 실무 사례
  6. 트러블슈팅
  7. 마무리

컨테이너 어댑터 개요

비교표

어댑터자료구조삽입삭제접근기반 컨테이너
stackLIFOpushpoptopdeque (기본)
queueFIFOpushpopfront/backdeque (기본)
priority_queuepushpoptopvector (기본)

실전 구현

1) stack (LIFO)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <stack>
int main() {
    std::stack<int> s;
    
    // push
    s.push(1);
    s.push(2);
    s.push(3);
    
    // top
    std::cout << s.top() << std::endl;  // 3
    
    // pop
    s.pop();
    std::cout << s.top() << std::endl;  // 2
    
    // size
    std::cout << s.size() << std::endl;  // 2
    
    // empty
    while (!s.empty()) {
        std::cout << s.top() << " ";
        s.pop();
    }
    std::cout << std::endl;  // 2 1
    
    return 0;
}

2) queue (FIFO)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <queue>
int main() {
    std::queue<int> q;
    
    // push
    q.push(1);
    q.push(2);
    q.push(3);
    
    // front/back
    std::cout << q.front() << std::endl;  // 1
    std::cout << q.back() << std::endl;   // 3
    
    // pop
    q.pop();
    std::cout << q.front() << std::endl;  // 2
    
    // size
    std::cout << q.size() << std::endl;  // 2
    
    // empty
    while (!q.empty()) {
        std::cout << q.front() << " ";
        q.pop();
    }
    std::cout << std::endl;  // 2 3
    
    return 0;
}

3) priority_queue (힙)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <queue>
#include <vector>
int main() {
    // 최대 힙 (기본)
    std::priority_queue<int> maxHeap;
    
    maxHeap.push(3);
    maxHeap.push(1);
    maxHeap.push(4);
    maxHeap.push(2);
    
    while (!maxHeap.empty()) {
        std::cout << maxHeap.top() << " ";  // 4 3 2 1
        maxHeap.pop();
    }
    std::cout << std::endl;
    
    // 최소 힙
    std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
    
    minHeap.push(3);
    minHeap.push(1);
    minHeap.push(4);
    minHeap.push(2);
    
    while (!minHeap.empty()) {
        std::cout << minHeap.top() << " ";  // 1 2 3 4
        minHeap.pop();
    }
    std::cout << std::endl;
    
    return 0;
}

4) 기반 컨테이너 선택

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <deque>
#include <iostream>
#include <list>
#include <queue>
#include <stack>
#include <vector>
int main() {
    // stack (기본: deque)
    std::stack<int> s1;
    std::stack<int, std::vector<int>> s2;  // vector 기반
    std::stack<int, std::list<int>> s3;    // list 기반
    
    // queue (기본: deque)
    std::queue<int> q1;
    std::queue<int, std::list<int>> q2;
    
    // priority_queue (기본: vector)
    std::priority_queue<int> pq1;
    std::priority_queue<int, std::deque<int>> pq2;
    
    return 0;
}

고급 활용

1) 괄호 검증 (stack)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <stack>
#include <string>
bool isValid(const std::string& s) {
    std::stack<char> st;
    
    for (char c : s) {
        if (c == '(' || c == '{' || c == '[') {
            st.push(c);
        } else {
            if (st.empty()) return false;
            
            char top = st.top();
            st.pop();
            
            if (c == ')' && top != '(') return false;
            if (c == '}' && top != '{') return false;
            if (c == ']' && top != '[') return false;
        }
    }
    
    return st.empty();
}
int main() {
    std::cout << isValid("()[]{}") << std::endl;     // 1
    std::cout << isValid("([)]") << std::endl;       // 0
    std::cout << isValid("{[()]}") << std::endl;     // 1
    
    return 0;
}

2) BFS (queue)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <queue>
#include <vector>
void bfs(const std::vector<std::vector<int>>& graph, int start) {
    std::vector<bool> visited(graph.size(), false);
    std::queue<int> q;
    
    q.push(start);
    visited[start] = true;
    
    while (!q.empty()) {
        int node = q.front();
        q.pop();
        
        std::cout << node << " ";
        
        for (int neighbor : graph[node]) {
            if (!visited[neighbor]) {
                visited[neighbor] = true;
                q.push(neighbor);
            }
        }
    }
    std::cout << std::endl;
}
int main() {
    std::vector<std::vector<int>> graph = {
        {1, 2},     // 0 → 1, 2
        {0, 3, 4},  // 1 → 0, 3, 4
        {0, 4},     // 2 → 0, 4
        {1},        // 3 → 1
        {1, 2}      // 4 → 1, 2
    };
    
    bfs(graph, 0);  // 0 1 2 3 4
    
    return 0;
}

3) 다익스트라 (priority_queue)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <queue>
#include <vector>
using Edge = std::pair<int, int>;  // {거리, 노드}
std::vector<int> dijkstra(const std::vector<std::vector<Edge>>& graph, int start) {
    int n = graph.size();
    std::vector<int> dist(n, INT_MAX);
    std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> pq;
    
    dist[start] = 0;
    pq.push({0, start});
    
    while (!pq.empty()) {
        auto [d, u] = pq.top();
        pq.pop();
        
        if (d > dist[u]) continue;
        
        for (auto [w, v] : graph[u]) {
            if (dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
                pq.push({dist[v], v});
            }
        }
    }
    
    return dist;
}
int main() {
    std::vector<std::vector<Edge>> graph = {
        {{1, 1}, {4, 2}},  // 0 → 1(1), 2(4)
        {{2, 2}, {5, 3}},  // 1 → 2(2), 3(5)
        {{1, 3}},          // 2 → 3(1)
        {}                 // 3
    };
    
    auto dist = dijkstra(graph, 0);
    
    for (int i = 0; i < dist.size(); ++i) {
        std::cout << "0 → " << i << ": " << dist[i] << std::endl;
    }
    
    return 0;
}

출력: 다음은 간단한 code 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

0 → 0: 0
0 → 1: 1
0 → 2: 3
0 → 3: 4

성능 비교

시간 복잡도

연산stackqueuepriority_queue
pushO(1)O(1)O(log n)
popO(1)O(1)O(log n)
top/frontO(1)O(1)O(1)
sizeO(1)O(1)O(1)
emptyO(1)O(1)O(1)

실무 사례

사례 1: 작업 스케줄러

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <queue>
#include <string>
#include <vector>
struct Task {
    int priority;
    std::string name;
    
    bool operator>(const Task& other) const {
        return priority > other.priority;  // 낮은 우선순위가 먼저
    }
};
int main() {
    std::priority_queue<Task, std::vector<Task>, std::greater<Task>> scheduler;
    
    scheduler.push({3, "낮은 우선순위"});
    scheduler.push({1, "높은 우선순위"});
    scheduler.push({2, "중간 우선순위"});
    
    while (!scheduler.empty()) {
        Task task = scheduler.top();
        scheduler.pop();
        
        std::cout << "실행: " << task.name << " (우선순위 " << task.priority << ")" << std::endl;
    }
    
    return 0;
}

출력:

실행: 높은 우선순위 (우선순위 1)
실행: 중간 우선순위 (우선순위 2)
실행: 낮은 우선순위 (우선순위 3)

사례 2: 되돌리기 (Undo)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <stack>
#include <string>
class TextEditor {
private:
    std::string text_;
    std::stack<std::string> history_;
    
public:
    void write(const std::string& str) {
        history_.push(text_);
        text_ += str;
    }
    
    void undo() {
        if (!history_.empty()) {
            text_ = history_.top();
            history_.pop();
        }
    }
    
    std::string getText() const {
        return text_;
    }
};
int main() {
    TextEditor editor;
    
    editor.write("Hello");
    std::cout << editor.getText() << std::endl;  // Hello
    
    editor.write(" World");
    std::cout << editor.getText() << std::endl;  // Hello World
    
    editor.undo();
    std::cout << editor.getText() << std::endl;  // Hello
    
    editor.undo();
    std::cout << editor.getText() << std::endl;  // (빈 문자열)
    
    return 0;
}

사례 3: 프린터 큐

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <queue>
#include <string>
struct PrintJob {
    int priority;
    std::string document;
    
    bool operator<(const PrintJob& other) const {
        return priority < other.priority;  // 높은 우선순위가 먼저
    }
};
int main() {
    std::priority_queue<PrintJob> printer;
    
    printer.push({1, "문서1.pdf"});
    printer.push({3, "긴급.pdf"});
    printer.push({2, "문서2.pdf"});
    
    while (!printer.empty()) {
        PrintJob job = printer.top();
        printer.pop();
        
        std::cout << "인쇄: " << job.document << " (우선순위 " << job.priority << ")" << std::endl;
    }
    
    return 0;
}

출력:

인쇄: 긴급.pdf (우선순위 3)
인쇄: 문서2.pdf (우선순위 2)
인쇄: 문서1.pdf (우선순위 1)

사례 4: DFS (stack)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <stack>
#include <vector>
void dfs(const std::vector<std::vector<int>>& graph, int start) {
    std::vector<bool> visited(graph.size(), false);
    std::stack<int> s;
    
    s.push(start);
    
    while (!s.empty()) {
        int node = s.top();
        s.pop();
        
        if (visited[node]) continue;
        
        visited[node] = true;
        std::cout << node << " ";
        
        for (int neighbor : graph[node]) {
            if (!visited[neighbor]) {
                s.push(neighbor);
            }
        }
    }
    std::cout << std::endl;
}
int main() {
    std::vector<std::vector<int>> graph = {
        {1, 2},     // 0 → 1, 2
        {0, 3, 4},  // 1 → 0, 3, 4
        {0, 4},     // 2 → 0, 4
        {1},        // 3 → 1
        {1, 2}      // 4 → 1, 2
    };
    
    dfs(graph, 0);  // 0 2 4 1 3
    
    return 0;
}

트러블슈팅

문제 1: top() 후 pop() 누락

증상: 메모리 누수 또는 중복 처리 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ pop 누락
std::stack<int> s;
s.push(1);
int x = s.top();
// s.pop() 누락!
// ✅ pop 호출
int x = s.top();
s.pop();

문제 2: empty() 체크 누락

증상: 미정의 동작 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ 빈 스택
std::stack<int> s;
// std::cout << s.top() << std::endl;  // 미정의 동작
// ✅ empty() 체크
if (!s.empty()) {
    std::cout << s.top() << std::endl;
}

문제 3: priority_queue 순서 혼동

증상: 의도와 다른 순서 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 기본: 최대 힙
std::priority_queue<int> maxHeap;
maxHeap.push(3);
maxHeap.push(1);
maxHeap.push(4);
std::cout << maxHeap.top() << std::endl;  // 4 (최대값)
// 최소 힙
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
minHeap.push(3);
minHeap.push(1);
minHeap.push(4);
std::cout << minHeap.top() << std::endl;  // 1 (최소값)

문제 4: 반복자 미제공

증상: 순회 불가 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 반복자 없음
std::stack<int> s;
s.push(1);
s.push(2);
// for (int x : s) {}  // 에러: 반복자 없음
// ✅ pop으로 순회
while (!s.empty()) {
    std::cout << s.top() << " ";
    s.pop();
}

마무리

컨테이너 어댑터기존 컨테이너를 특정 인터페이스로 감싼 것입니다.

핵심 요약

  1. stack (LIFO)
    • push, pop, top
    • DFS, 괄호 검증, 되돌리기
    • O(1)
  2. queue (FIFO)
    • push, pop, front, back
    • BFS, 작업 큐
    • O(1)
  3. priority_queue (힙)
    • push, pop, top
    • 다익스트라, 작업 스케줄링
    • O(log n)
  4. 기반 컨테이너
    • stack/queue: deque (기본)
    • priority_queue: vector (기본)

선택 가이드

상황권장이유
DFSstackLIFO
BFSqueueFIFO
다익스트라priority_queue최소값
괄호 검증stackLIFO
작업 큐queueFIFO
작업 스케줄링priority_queue우선순위

코드 예제 치트시트

다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// stack
std::stack<int> s;
s.push(1);
s.top();
s.pop();
// queue
std::queue<int> q;
q.push(1);
q.front();
q.back();
q.pop();
// priority_queue (최대 힙)
std::priority_queue<int> maxHeap;
maxHeap.push(1);
maxHeap.top();
maxHeap.pop();
// priority_queue (최소 힙)
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;

다음 단계

참고 자료

  • “The C++ Standard Library” - Nicolai M. Josuttis
  • “Effective STL” - Scott Meyers
  • cppreference: https://en.cppreference.com/w/cpp/container 한 줄 정리: stack은 LIFO, queue는 FIFO, priority_queue는 힙으로, 각각 DFS·BFS·다익스트라 등의 알고리즘에 활용된다.

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3