第2章:課題の実装

はじめに

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

---

1. ex00: Start with a few functions

1.1 課題の要件

  • swap: 2つの値を交換
  • min: 2つの値の小さい方を返す
  • max: 2つの値の大きい方を返す
  • 同じ値の場合は2番目を返す
  • 1.2 実装

    // whatever.hpp
    #ifndef WHATEVER_HPP
    #define WHATEVER_HPP
    
    template <typename T>
    void swap(T& a, T& b) {
        T temp = a;
        a = b;
        b = temp;
    }
    
    template <typename T>
    T const& min(T const& a, T const& b) {
        return (a < b) ? a : b;
    }
    
    template <typename T>
    T const& max(T const& a, T const& b) {
        return (a > b) ? a : b;
    }
    
    #endif
    
    // main.cpp
    #include "whatever.hpp"
    #include <iostream>
    #include <string>
    
    int main() {
        int a = 2;
        int b = 3;
    
        ::swap(a, b);
        std::cout << "a = " << a << ", b = " << b << std::endl;
        std::cout << "min(a, b) = " << ::min(a, b) << std::endl;
        std::cout << "max(a, b) = " << ::max(a, b) << std::endl;
    
        std::string c = "chaine1";
        std::string d = "chaine2";
    
        ::swap(c, d);
        std::cout << "c = " << c << ", d = " << d << std::endl;
        std::cout << "min(c, d) = " << ::min(c, d) << std::endl;
        std::cout << "max(c, d) = " << ::max(c, d) << std::endl;
    
        return 0;
    }
    

    1.3 期待される出力

    a = 3, b = 2
    min(a, b) = 2
    max(a, b) = 3
    c = chaine2, d = chaine1
    min(c, d) = chaine1
    max(c, d) = chaine2
    

    1.4 なぜconst参照を返すか

    // 参照を返すことで、一時オブジェクトのコピーを避ける
    template <typename T>
    T const& min(T const& a, T const& b) {
        return (a < b) ? a : b;
    }
    
    // 値を返す場合、コピーが発生
    template <typename T>
    T min(T a, T b) {
        return (a < b) ? a : b;  // コピーが発生
    }
    

    ---

    2. ex01: Iter

    2.1 課題の要件

  • 配列、長さ、関数ポインタを受け取る
  • 配列の各要素に関数を適用
  • 2.2 実装

    // iter.hpp
    #ifndef ITER_HPP
    #define ITER_HPP
    
    #include <cstddef>
    
    template <typename T>
    void iter(T* array, size_t length, void (*func)(T&)) {
        for (size_t i = 0; i < length; i++) {
            func(array[i]);
        }
    }
    
    // const版
    template <typename T>
    void iter(const T* array, size_t length, void (*func)(const T&)) {
        for (size_t i = 0; i < length; i++) {
            func(array[i]);
        }
    }
    
    #endif
    
    // main.cpp
    #include "iter.hpp"
    #include <iostream>
    #include <string>
    
    template <typename T>
    void print(const T& value) {
        std::cout << value << std::endl;
    }
    
    template <typename T>
    void increment(T& value) {
        value++;
    }
    
    void toUpper(std::string& str) {
        for (size_t i = 0; i < str.length(); i++) {
            str[i] = std::toupper(str[i]);
        }
    }
    
    int main() {
        std::cout << "=== Integer Array ===" << std::endl;
        int intArr[] = {1, 2, 3, 4, 5};
        size_t intLen = sizeof(intArr) / sizeof(intArr[0]);
    
        std::cout << "Original:" << std::endl;
        iter(intArr, intLen, print);
    
        iter(intArr, intLen, increment);
        std::cout << "After increment:" << std::endl;
        iter(intArr, intLen, print);
    
        std::cout << "\n=== String Array ===" << std::endl;
        std::string strArr[] = {"hello", "world", "cpp"};
        size_t strLen = sizeof(strArr) / sizeof(strArr[0]);
    
        std::cout << "Original:" << std::endl;
        iter(strArr, strLen, print);
    
        iter(strArr, strLen, toUpper);
        std::cout << "After toUpper:" << std::endl;
        iter(strArr, strLen, print);
    
        return 0;
    }
    

    2.3 期待される出力

    === Integer Array ===
    Original:
    1
    2
    3
    4
    5
    After increment:
    2
    3
    4
    5
    6
    
    === String Array ===
    Original:
    hello
    world
    cpp
    After toUpper:
    HELLO
    WORLD
    CPP
    

    2.4 テンプレート関数を渡す

    // テンプレート関数を渡す場合、明示的なインスタンス化が必要
    template <typename T>
    void genericPrint(const T& value) {
        std::cout << value << std::endl;
    }
    
    // 使用時に明示的に型を指定
    iter(intArr, intLen, genericPrint<int>);
    iter(strArr, strLen, genericPrint<std::string>);
    

    ---

    3. ex02: Array

    3.1 課題の要件

  • テンプレートクラスArray
  • デフォルトコンストラクタ(空の配列)
  • unsigned int nのコンストラクタ(n要素、デフォルト初期化)
  • コピーコンストラクタと代入演算子(深いコピー)
  • operator[]で要素アクセス
  • 範囲外でstd::exception派生の例外
  • 3.2 実装

    // Array.hpp
    #ifndef ARRAY_HPP
    #define ARRAY_HPP
    
    #include <exception>
    #include <cstddef>
    
    template <typename T>
    class Array {
    private:
        T* _elements;
        unsigned int _size;
    
    public:
        // コンストラクタ
        Array() : _elements(NULL), _size(0) {}
    
        Array(unsigned int n) : _elements(new T[n]()), _size(n) {}
    
        Array(const Array& other) : _elements(NULL), _size(0) {
            *this = other;
        }
    
        // 代入演算子
        Array& operator=(const Array& other) {
            if (this != &other) {
                delete[] _elements;
                _size = other._size;
                _elements = new T[_size];
                for (unsigned int i = 0; i < _size; i++) {
                    _elements[i] = other._elements[i];
                }
            }
            return *this;
        }
    
        // デストラクタ
        ~Array() {
            delete[] _elements;
        }
    
        // 添字演算子
        T& operator[](unsigned int index) {
            if (index >= _size) {
                throw OutOfBoundsException();
            }
            return _elements[index];
        }
    
        const T& operator[](unsigned int index) const {
            if (index >= _size) {
                throw OutOfBoundsException();
            }
            return _elements[index];
        }
    
        // サイズ取得
        unsigned int size() const {
            return _size;
        }
    
        // 例外クラス
        class OutOfBoundsException : public std::exception {
        public:
            const char* what() const throw() {
                return "Index out of bounds";
            }
        };
    };
    
    #endif
    
    // main.cpp
    #include "Array.hpp"
    #include <iostream>
    #include <string>
    
    int main() {
        std::cout << "=== Default Constructor ===" << std::endl;
        Array<int> empty;
        std::cout << "Empty array size: " << empty.size() << std::endl;
    
        std::cout << "\n=== Sized Constructor ===" << std::endl;
        Array<int> arr(5);
        std::cout << "Array size: " << arr.size() << std::endl;
    
        std::cout << "Default values: ";
        for (unsigned int i = 0; i < arr.size(); i++) {
            std::cout << arr[i] << " ";
        }
        std::cout << std::endl;
    
        std::cout << "\n=== Assignment ===" << std::endl;
        for (unsigned int i = 0; i < arr.size(); i++) {
            arr[i] = i * 10;
        }
        std::cout << "After assignment: ";
        for (unsigned int i = 0; i < arr.size(); i++) {
            std::cout << arr[i] << " ";
        }
        std::cout << std::endl;
    
        std::cout << "\n=== Copy Constructor ===" << std::endl;
        Array<int> copy(arr);
        std::cout << "Copy: ";
        for (unsigned int i = 0; i < copy.size(); i++) {
            std::cout << copy[i] << " ";
        }
        std::cout << std::endl;
    
        std::cout << "\n=== Deep Copy Test ===" << std::endl;
        arr[0] = 999;
        std::cout << "Original[0]: " << arr[0] << std::endl;
        std::cout << "Copy[0]: " << copy[0] << std::endl;
    
        std::cout << "\n=== Out of Bounds ===" << std::endl;
        try {
            arr[100] = 42;
        } catch (const std::exception& e) {
            std::cout << "Exception: " << e.what() << std::endl;
        }
    
        std::cout << "\n=== String Array ===" << std::endl;
        Array<std::string> strArr(3);
        strArr[0] = "Hello";
        strArr[1] = "World";
        strArr[2] = "!";
    
        for (unsigned int i = 0; i < strArr.size(); i++) {
            std::cout << strArr[i] << " ";
        }
        std::cout << std::endl;
    
        return 0;
    }
    

    3.3 期待される出力

    === Default Constructor ===
    Empty array size: 0
    
    === Sized Constructor ===
    Array size: 5
    Default values: 0 0 0 0 0
    
    === Assignment ===
    After assignment: 0 10 20 30 40
    
    === Copy Constructor ===
    Copy: 0 10 20 30 40
    
    === Deep Copy Test ===
    Original[0]: 999
    Copy[0]: 0
    
    === Out of Bounds ===
    Exception: Index out of bounds
    
    === String Array ===
    Hello World !
    

    3.4 デフォルト初期化

    // new T[n]() で値初期化(ゼロ初期化)
    Array(unsigned int n) : _elements(new T[n]()), _size(n) {}
    
    // new T[n] だとデフォルト初期化(不定値の可能性)
    // POD型では初期化されない
    

    ---

    4. 実装の注意点

    4.1 std::swapとの衝突

    // 課題のswapとstd::swapの衝突を避ける
    #include "whatever.hpp"
    
    ::swap(a, b);  // グローバルスコープのswap
    std::swap(a, b);  // 標準ライブラリのswap
    

    4.2 const正当性

    // const版と非const版の両方が必要
    T& operator[](unsigned int index);
    const T& operator[](unsigned int index) const;
    
    // 使用
    Array<int> arr(5);
    const Array<int>& constRef = arr;
    
    arr[0] = 10;      // 非const版
    int x = constRef[0];  // const版
    

    4.3 例外安全性

    // 代入演算子での例外安全性
    Array& operator=(const Array& other) {
        if (this != &other) {
            // 新しい配列を先に作成
            T* newElements = new T[other._size];
            for (unsigned int i = 0; i < other._size; i++) {
                newElements[i] = other._elements[i];
            }
            // 成功したら古い配列を削除
            delete[] _elements;
            _elements = newElements;
            _size = other._size;
        }
        return *this;
    }
    

    ---

    まとめ

    本章で実装した内容:

  • ex00: 基本的な関数テンプレート(swap, min, max)
  • ex01: 配列と関数ポインタのテンプレート(iter)
  • ex02: クラステンプレート(Array)