第4章:stack - コンテナアダプタ

はじめに

stackは、既存のコンテナを包んでLIFO(Last In, First Out)の動作を提供するコンテナアダプタです。本章では、アダプタパターンとstackの実装を学びます。

---

1. コンテナアダプタとは

1.1 デザインパターン:アダプタ

アダプタパターン:
既存のインターフェースを別のインターフェースに変換

+--------+     +--------+     +--------+
| Client | --> | Adapter| --> |Adaptee |
+--------+     +--------+     +--------+

STLでの例:
stack, queue, priority_queue

これらは新しいデータ構造ではなく、
既存のコンテナ(vector, deque, list)を
特定のインターフェースで包む

1.2 stackの特性

LIFO (Last In, First Out):
後から入れたものが先に出る

Push:               Pop:
+-----+            +-----+
|  3  | ← top      |     |
+-----+            +-----+
|  2  |            |  2  | ← new top
+-----+            +-----+
|  1  |            |  1  |
+-----+            +-----+

操作:
- push: 上に追加
- pop: 上から削除
- top: 一番上を参照

1.3 使用例

ft::stack<int> s;

s.push(1);  // [1]
s.push(2);  // [1, 2]
s.push(3);  // [1, 2, 3]

s.top();    // 3
s.pop();    // [1, 2]
s.top();    // 2

s.size();   // 2
s.empty();  // false

---

2. 実装

2.1 クラス定義

namespace ft {

template<typename T, typename Container = ft::vector<T> >
class stack {
public:
    typedef T           value_type;
    typedef Container   container_type;
    typedef size_t      size_type;

protected:
    container_type c;

public:
    // コンストラクタ
    explicit stack(const container_type& ctnr = container_type())
        : c(ctnr) {}

    // 容量
    bool empty() const {
        return c.empty();
    }

    size_type size() const {
        return c.size();
    }

    // 要素アクセス
    value_type& top() {
        return c.back();
    }

    const value_type& top() const {
        return c.back();
    }

    // 変更
    void push(const value_type& val) {
        c.push_back(val);
    }

    void pop() {
        c.pop_back();
    }

    // フレンド宣言(比較演算子のため)
    template<typename T1, typename C1>
    friend bool operator==(const stack<T1, C1>&, const stack<T1, C1>&);

    template<typename T1, typename C1>
    friend bool operator<(const stack<T1, C1>&, const stack<T1, C1>&);
};

} // namespace ft

2.2 比較演算子

template<typename T, typename Container>
bool operator==(const stack<T, Container>& lhs,
                const stack<T, Container>& rhs) {
    return lhs.c == rhs.c;
}

template<typename T, typename Container>
bool operator!=(const stack<T, Container>& lhs,
                const stack<T, Container>& rhs) {
    return !(lhs == rhs);
}

template<typename T, typename Container>
bool operator<(const stack<T, Container>& lhs,
               const stack<T, Container>& rhs) {
    return lhs.c < rhs.c;
}

template<typename T, typename Container>
bool operator<=(const stack<T, Container>& lhs,
                const stack<T, Container>& rhs) {
    return !(rhs < lhs);
}

template<typename T, typename Container>
bool operator>(const stack<T, Container>& lhs,
               const stack<T, Container>& rhs) {
    return rhs < lhs;
}

template<typename T, typename Container>
bool operator>=(const stack<T, Container>& lhs,
                const stack<T, Container>& rhs) {
    return !(lhs < rhs);
}

---

3. 基底コンテナの要件

3.1 必要なインターフェース

stackが使用するコンテナは、以下のメンバを持つ必要があります:

// 必須メンバ
empty()     // 空かどうか
size()      // サイズ
back()      // 末尾の要素
push_back() // 末尾に追加
pop_back()  // 末尾を削除

// これを満たすコンテナ:
// - vector
// - deque
// - list

3.2 異なるコンテナでの使用

// デフォルト(vector)
ft::stack<int> s1;

// dequeを使用
ft::stack<int, std::deque<int> > s2;

// listを使用
ft::stack<int, std::list<int> > s3;

// 独自のコンテナも可能
// (必要なインターフェースを持っていれば)

---

4. protected継承について

4.1 なぜprotectedか

STL標準では、基底コンテナはprotectedメンバとして定義されます:

protected:
    container_type c;

理由:

  • 派生クラスがアクセス可能: 拡張が可能
  • 外部からは非公開: カプセル化を維持
  • 標準準拠: STL仕様に従う

4.2 比較演算子のフレンド

比較演算子は非メンバ関数ですが、cにアクセスする必要があります:

template<typename T1, typename C1>
friend bool operator==(const stack<T1, C1>&, const stack<T1, C1>&);

---

5. 計算量

| 操作 | 計算量(vectorの場合) | |------|------------------------| | push | 償却O(1) | | pop | O(1) | | top | O(1) | | size | O(1) | | empty | O(1) |

---

6. 実用例

6.1 括弧のバランスチェック

bool isBalanced(const std::string& str) {
    ft::stack<char> s;

    for (size_t i = 0; i < str.size(); ++i) {
        char c = str[i];

        if (c == '(' || c == '[' || c == '{') {
            s.push(c);
        } else if (c == ')' || c == ']' || c == '}') {
            if (s.empty())
                return false;

            char top = s.top();
            s.pop();

            if ((c == ')' && top != '(') ||
                (c == ']' && top != '[') ||
                (c == '}' && top != '{')) {
                return false;
            }
        }
    }

    return s.empty();
}

6.2 逆ポーランド記法(RPN)計算機

int evalRPN(const std::vector<std::string>& tokens) {
    ft::stack<int> s;

    for (size_t i = 0; i < tokens.size(); ++i) {
        const std::string& token = tokens[i];

        if (token == "+" || token == "-" ||
            token == "*" || token == "/") {
            int b = s.top(); s.pop();
            int a = s.top(); s.pop();

            if (token == "+") s.push(a + b);
            else if (token == "-") s.push(a - b);
            else if (token == "*") s.push(a * b);
            else if (token == "/") s.push(a / b);
        } else {
            s.push(atoi(token.c_str()));
        }
    }

    return s.top();
}

6.3 深さ優先探索(DFS)

void dfs(Graph& graph, int start) {
    ft::stack<int> s;
    std::set<int> visited;

    s.push(start);

    while (!s.empty()) {
        int node = s.top();
        s.pop();

        if (visited.count(node))
            continue;

        visited.insert(node);
        std::cout << node << " ";

        // 隣接ノードをスタックに追加
        for (int neighbor : graph.neighbors(node)) {
            if (!visited.count(neighbor)) {
                s.push(neighbor);
            }
        }
    }
}

---

7. queueとの比較

7.1 queue(参考)

// queueはFIFO(First In, First Out)
template<typename T, typename Container = std::deque<T> >
class queue {
protected:
    Container c;

public:
    // 先頭と末尾の両方にアクセスが必要
    value_type& front() { return c.front(); }
    value_type& back() { return c.back(); }

    void push(const value_type& val) { c.push_back(val); }
    void pop() { c.pop_front(); }
};

7.2 違い

| | stack | queue | |--|-------|-------| | 方式 | LIFO | FIFO | | 追加 | 上(back) | 後ろ(back) | | 削除 | 上(back) | 前(front) | | アクセス | top (back) | front, back | | デフォルト | vector | deque |

---

まとめ

本章で学んだこと:

  • コンテナアダプタ: 既存コンテナを包むパターン
  • stackの実装: シンプルなラッパー
  • 基底コンテナの要件: back, push_back, pop_back
  • 計算量: すべてO(1)(または償却O(1))
  • 実用例: 括弧チェック、RPN、DFS

次章では、イテレータとユーティリティを学びます。