第2章:課題の実装

はじめに

本章では、CPP Module 08の各演習を実装します。

---

1. ex00: Easy find

1.1 課題の要件

  • 関数テンプレートeasyfindを実装
  • 第1引数: 整数型のコンテナ
  • 第2引数: 検索する整数
  • 見つかったらその位置のイテレータを返す
  • 見つからなければ例外を投げる
  • 1.2 実装

    // easyfind.hpp
    #ifndef EASYFIND_HPP
    #define EASYFIND_HPP
    
    #include <algorithm>
    #include <exception>
    
    class NotFoundException : public std::exception {
    public:
        const char* what() const throw() {
            return "Element not found in container";
        }
    };
    
    template <typename T>
    typename T::iterator easyfind(T& container, int value) {
        typename T::iterator it = std::find(container.begin(), container.end(), value);
        if (it == container.end()) {
            throw NotFoundException();
        }
        return it;
    }
    
    // const版
    template <typename T>
    typename T::const_iterator easyfind(const T& container, int value) {
        typename T::const_iterator it = std::find(container.begin(), container.end(), value);
        if (it == container.end()) {
            throw NotFoundException();
        }
        return it;
    }
    
    #endif
    
    // main.cpp
    #include "easyfind.hpp"
    #include <iostream>
    #include <vector>
    #include <list>
    #include <deque>
    
    int main() {
        std::cout << "=== Vector Test ===" << std::endl;
        std::vector<int> vec;
        for (int i = 0; i < 10; i++) {
            vec.push_back(i * 2);
        }
    
        try {
            std::vector<int>::iterator it = easyfind(vec, 6);
            std::cout << "Found: " << *it << std::endl;
            std::cout << "Position: " << std::distance(vec.begin(), it) << std::endl;
        } catch (const std::exception& e) {
            std::cout << e.what() << std::endl;
        }
    
        try {
            easyfind(vec, 7);
        } catch (const std::exception& e) {
            std::cout << "7 not found: " << e.what() << std::endl;
        }
    
        std::cout << "\n=== List Test ===" << std::endl;
        std::list<int> lst;
        for (int i = 0; i < 5; i++) {
            lst.push_back(i * 10);
        }
    
        try {
            std::list<int>::iterator it = easyfind(lst, 30);
            std::cout << "Found: " << *it << std::endl;
        } catch (const std::exception& e) {
            std::cout << e.what() << std::endl;
        }
    
        std::cout << "\n=== Deque Test ===" << std::endl;
        std::deque<int> deq;
        deq.push_back(100);
        deq.push_back(200);
        deq.push_back(300);
    
        try {
            std::deque<int>::iterator it = easyfind(deq, 200);
            std::cout << "Found: " << *it << std::endl;
        } catch (const std::exception& e) {
            std::cout << e.what() << std::endl;
        }
    
        return 0;
    }
    

    1.3 期待される出力

    === Vector Test ===
    Found: 6
    Position: 3
    7 not found: Element not found in container
    
    === List Test ===
    Found: 30
    
    === Deque Test ===
    Found: 200
    

    1.4 なぜtypenameが必要か

    template <typename T>
    typename T::iterator easyfind(T& container, int value) {
        // ...
    }
    

    T::iterator依存名(dependent name)です。コンパイラはTが何であるかを知らないため、T::iteratorが型なのか静的メンバなのか判断できません。

    // Tが何であるかによって解釈が変わる
    T::iterator * x;  // 型なら: iterator型のポインタxを宣言
                      // 静的メンバなら: iterator * x の乗算
    

    typenameキーワードは、これが型であることをコンパイラに伝えます。

    ---

    2. ex01: Span

    2.1 課題の要件

  • クラスSpanを実装
  • 最大N個の整数を格納
  • addNumber: 1つの数を追加
  • shortestSpan: 最小の差を返す
  • longestSpan: 最大の差を返す
  • イテレータ範囲で追加する関数も実装
  • 2.2 実装

    // Span.hpp
    #ifndef SPAN_HPP
    #define SPAN_HPP
    
    #include <vector>
    #include <algorithm>
    #include <exception>
    #include <limits>
    
    class Span {
    private:
        std::vector<int> _numbers;
        unsigned int _capacity;
    
    public:
        // コンストラクタ
        Span();
        Span(unsigned int n);
        Span(const Span& other);
        Span& operator=(const Span& other);
        ~Span();
    
        // メンバ関数
        void addNumber(int number);
    
        template <typename Iterator>
        void addNumbers(Iterator begin, Iterator end) {
            size_t distance = std::distance(begin, end);
            if (_numbers.size() + distance > _capacity) {
                throw CapacityExceededException();
            }
            _numbers.insert(_numbers.end(), begin, end);
        }
    
        int shortestSpan() const;
        int longestSpan() const;
    
        // ゲッター
        unsigned int size() const;
        unsigned int capacity() const;
    
        // 例外クラス
        class CapacityExceededException : public std::exception {
        public:
            const char* what() const throw() {
                return "Span capacity exceeded";
            }
        };
    
        class NotEnoughElementsException : public std::exception {
        public:
            const char* what() const throw() {
                return "Not enough elements to calculate span";
            }
        };
    };
    
    #endif
    
    // Span.cpp
    #include "Span.hpp"
    
    Span::Span() : _capacity(0) {}
    
    Span::Span(unsigned int n) : _capacity(n) {
        _numbers.reserve(n);
    }
    
    Span::Span(const Span& other)
        : _numbers(other._numbers), _capacity(other._capacity) {}
    
    Span& Span::operator=(const Span& other) {
        if (this != &other) {
            _numbers = other._numbers;
            _capacity = other._capacity;
        }
        return *this;
    }
    
    Span::~Span() {}
    
    void Span::addNumber(int number) {
        if (_numbers.size() >= _capacity) {
            throw CapacityExceededException();
        }
        _numbers.push_back(number);
    }
    
    int Span::shortestSpan() const {
        if (_numbers.size() < 2) {
            throw NotEnoughElementsException();
        }
    
        // ソートしたコピーを作成
        std::vector<int> sorted = _numbers;
        std::sort(sorted.begin(), sorted.end());
    
        // 隣接要素の差の最小値を求める
        int minSpan = std::numeric_limits<int>::max();
        for (size_t i = 1; i < sorted.size(); i++) {
            int diff = sorted[i] - sorted[i - 1];
            if (diff < minSpan) {
                minSpan = diff;
            }
        }
    
        return minSpan;
    }
    
    int Span::longestSpan() const {
        if (_numbers.size() < 2) {
            throw NotEnoughElementsException();
        }
    
        // 最小値と最大値の差
        std::vector<int>::const_iterator minIt = std::min_element(_numbers.begin(), _numbers.end());
        std::vector<int>::const_iterator maxIt = std::max_element(_numbers.begin(), _numbers.end());
    
        return *maxIt - *minIt;
    }
    
    unsigned int Span::size() const {
        return _numbers.size();
    }
    
    unsigned int Span::capacity() const {
        return _capacity;
    }
    
    // main.cpp
    #include "Span.hpp"
    #include <iostream>
    #include <cstdlib>
    #include <ctime>
    
    int main() {
        std::cout << "=== Basic Test ===" << std::endl;
        Span sp = Span(5);
    
        sp.addNumber(6);
        sp.addNumber(3);
        sp.addNumber(17);
        sp.addNumber(9);
        sp.addNumber(11);
    
        std::cout << "Shortest span: " << sp.shortestSpan() << std::endl;
        std::cout << "Longest span: " << sp.longestSpan() << std::endl;
    
        std::cout << "\n=== Large Test (10000 elements) ===" << std::endl;
        Span bigSpan(10000);
    
        std::srand(std::time(NULL));
        for (int i = 0; i < 10000; i++) {
            bigSpan.addNumber(std::rand());
        }
    
        std::cout << "Shortest span: " << bigSpan.shortestSpan() << std::endl;
        std::cout << "Longest span: " << bigSpan.longestSpan() << std::endl;
    
        std::cout << "\n=== Range Insert Test ===" << std::endl;
        Span rangeSpan(100);
        std::vector<int> nums;
        for (int i = 0; i < 50; i++) {
            nums.push_back(i * 2);
        }
    
        rangeSpan.addNumbers(nums.begin(), nums.end());
        std::cout << "Size after range insert: " << rangeSpan.size() << std::endl;
        std::cout << "Shortest span: " << rangeSpan.shortestSpan() << std::endl;
        std::cout << "Longest span: " << rangeSpan.longestSpan() << std::endl;
    
        std::cout << "\n=== Exception Test ===" << std::endl;
    
        // 容量超過
        try {
            Span smallSpan(2);
            smallSpan.addNumber(1);
            smallSpan.addNumber(2);
            smallSpan.addNumber(3);  // 例外
        } catch (const std::exception& e) {
            std::cout << "Capacity exception: " << e.what() << std::endl;
        }
    
        // 要素不足
        try {
            Span emptySpan(5);
            emptySpan.addNumber(1);
            emptySpan.shortestSpan();  // 例外
        } catch (const std::exception& e) {
            std::cout << "Not enough elements: " << e.what() << std::endl;
        }
    
        return 0;
    }
    

    2.3 期待される出力

    === Basic Test ===
    Shortest span: 2
    Longest span: 14
    
    === Large Test (10000 elements) ===
    Shortest span: 0
    Longest span: 2147483647
    
    === Range Insert Test ===
    Size after range insert: 50
    Shortest span: 2
    Longest span: 98
    
    === Exception Test ===
    Capacity exception: Span capacity exceeded
    Not enough elements: Not enough elements to calculate span
    

    2.4 効率的なshortestSpanの計算

    ソートを使用する理由:

    // 非効率: O(n^2)
    int shortestSpan() const {
        int minSpan = INT_MAX;
        for (size_t i = 0; i < _numbers.size(); i++) {
            for (size_t j = i + 1; j < _numbers.size(); j++) {
                int diff = std::abs(_numbers[i] - _numbers[j]);
                minSpan = std::min(minSpan, diff);
            }
        }
        return minSpan;
    }
    
    // 効率的: O(n log n)
    int shortestSpan() const {
        std::vector<int> sorted = _numbers;
        std::sort(sorted.begin(), sorted.end());  // O(n log n)
    
        int minSpan = INT_MAX;
        for (size_t i = 1; i < sorted.size(); i++) {
            minSpan = std::min(minSpan, sorted[i] - sorted[i-1]);
        }
        return minSpan;
    }
    

    ソート後は隣接要素のみを比較すればよいため、O(n)で最小差を見つけられます。

    ---

    3. ex02: Mutated abomination

    3.1 課題の要件

  • std::stackをイテレート可能にする
  • MutantStackクラスを実装
  • std::stackのすべての機能を維持
  • イテレータを追加
  • 3.2 std::stackの内部構造

    // std::stackの定義(簡略化)
    template <typename T, typename Container = std::deque<T>>
    class stack {
    protected:
        Container c;  // 内部コンテナ
    
    public:
        void push(const T& value) { c.push_back(value); }
        void pop() { c.pop_back(); }
        T& top() { return c.back(); }
        bool empty() const { return c.empty(); }
        size_t size() const { return c.size(); }
    };
    

    std::stackアダプタパターンで実装されています。内部コンテナcはprotectedメンバなので、派生クラスからアクセス可能です。

    3.3 実装

    // MutantStack.hpp
    #ifndef MUTANTSTACK_HPP
    #define MUTANTSTACK_HPP
    
    #include <stack>
    
    template <typename T>
    class MutantStack : public std::stack<T> {
    public:
        // コンストラクタ・デストラクタ
        MutantStack() : std::stack<T>() {}
        MutantStack(const MutantStack& other) : std::stack<T>(other) {}
        MutantStack& operator=(const MutantStack& other) {
            if (this != &other) {
                std::stack<T>::operator=(other);
            }
            return *this;
        }
        ~MutantStack() {}
    
        // イテレータ型の定義
        typedef typename std::stack<T>::container_type::iterator iterator;
        typedef typename std::stack<T>::container_type::const_iterator const_iterator;
        typedef typename std::stack<T>::container_type::reverse_iterator reverse_iterator;
        typedef typename std::stack<T>::container_type::const_reverse_iterator const_reverse_iterator;
    
        // イテレータ取得
        iterator begin() { return this->c.begin(); }
        iterator end() { return this->c.end(); }
    
        const_iterator begin() const { return this->c.begin(); }
        const_iterator end() const { return this->c.end(); }
    
        reverse_iterator rbegin() { return this->c.rbegin(); }
        reverse_iterator rend() { return this->c.rend(); }
    
        const_reverse_iterator rbegin() const { return this->c.rbegin(); }
        const_reverse_iterator rend() const { return this->c.rend(); }
    };
    
    #endif
    
    // main.cpp
    #include "MutantStack.hpp"
    #include <iostream>
    #include <list>
    
    int main() {
        std::cout << "=== MutantStack Test ===" << std::endl;
    
        MutantStack<int> mstack;
    
        mstack.push(5);
        mstack.push(17);
    
        std::cout << "Top: " << mstack.top() << std::endl;
    
        mstack.pop();
    
        std::cout << "Size: " << mstack.size() << std::endl;
    
        mstack.push(3);
        mstack.push(5);
        mstack.push(737);
        mstack.push(0);
    
        std::cout << "\n=== Iterator Test ===" << std::endl;
        MutantStack<int>::iterator it = mstack.begin();
        MutantStack<int>::iterator ite = mstack.end();
    
        ++it;
        --it;
    
        std::cout << "Elements: ";
        while (it != ite) {
            std::cout << *it << " ";
            ++it;
        }
        std::cout << std::endl;
    
        std::cout << "\n=== Reverse Iterator Test ===" << std::endl;
        std::cout << "Reverse: ";
        for (MutantStack<int>::reverse_iterator rit = mstack.rbegin();
             rit != mstack.rend(); ++rit) {
            std::cout << *rit << " ";
        }
        std::cout << std::endl;
    
        std::cout << "\n=== Copy Test ===" << std::endl;
        MutantStack<int> copy(mstack);
        std::cout << "Copy top: " << copy.top() << std::endl;
        std::cout << "Copy size: " << copy.size() << std::endl;
    
        std::cout << "\n=== std::stack compatibility ===" << std::endl;
        std::stack<int> s(mstack);
        std::cout << "std::stack top: " << s.top() << std::endl;
    
        return 0;
    }
    

    3.4 期待される出力

    === MutantStack Test ===
    Top: 17
    Size: 1
    
    === Iterator Test ===
    Elements: 5 3 5 737 0
    
    === Reverse Iterator Test ===
    Reverse: 0 737 5 3 5
    
    === Copy Test ===
    Copy top: 0
    Copy size: 5
    
    === std::stack compatibility ===
    std::stack top: 0
    

    3.5 std::listとの比較

    課題では、std::listと同じ動作をすることを確認します:

    // MutantStack版
    MutantStack<int> mstack;
    mstack.push(5);
    mstack.push(17);
    // ...
    for (auto it = mstack.begin(); it != mstack.end(); ++it) {
        std::cout << *it << " ";
    }
    
    // std::list版
    std::list<int> lst;
    lst.push_back(5);
    lst.push_back(17);
    // ...
    for (auto it = lst.begin(); it != lst.end(); ++it) {
        std::cout << *it << " ";
    }
    

    出力は同じになるべきです。

    3.6 なぜthis->cが必要か

    iterator begin() { return this->c.begin(); }
    

    テンプレートクラスで基底クラスのメンバにアクセスする場合、this->を使わないと依存名として認識されません。

    // エラーになる可能性
    iterator begin() { return c.begin(); }  // cが見つからない
    
    // 正しい
    iterator begin() { return this->c.begin(); }  // this経由でアクセス
    

    ---

    4. 実装の注意点

    4.1 イテレータの無効化

    // 危険なコード
    std::vector<int> vec = {1, 2, 3, 4, 5};
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        if (*it == 3) {
            vec.erase(it);  // イテレータが無効化される
        }
    }
    
    // 安全なコード
    for (auto it = vec.begin(); it != vec.end(); ) {
        if (*it == 3) {
            it = vec.erase(it);  // 次の有効なイテレータを取得
        } else {
            ++it;
        }
    }
    

    4.2 constの正しい使用

    // 読み取り専用の場合はconst版を使用
    template <typename T>
    typename T::const_iterator easyfind(const T& container, int value) {
        return std::find(container.begin(), container.end(), value);
    }
    
    // Spanのアクセサはconst
    int Span::shortestSpan() const {
        // _numbersを変更しない
    }
    

    4.3 例外安全性

    void Span::addNumber(int number) {
        // 事前条件のチェック
        if (_numbers.size() >= _capacity) {
            throw CapacityExceededException();
        }
        // 条件を満たしてから変更
        _numbers.push_back(number);
    }
    

    ---

    まとめ

    本章で実装した内容:

  • ex00: easyfind関数テンプレート
  • ex01: Spanクラス(整数コンテナ)
  • ex02: MutantStack(イテレート可能なスタック)