第2章:課題の実装

はじめに

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

---

1. ex00: Conversion of scalar types

1.1 課題の要件

  • ScalarConverterクラス(static メソッドのみ)
  • 文字列リテラルをchar, int, float, doubleに変換
  • 特殊値(nan, inf)のサポート
  • インスタンス化不可
  • 1.2 ScalarConverterクラス

    // ScalarConverter.hpp
    #ifndef SCALARCONVERTER_HPP
    #define SCALARCONVERTER_HPP
    
    #include <string>
    #include <iostream>
    #include <cstdlib>
    #include <limits>
    #include <cmath>
    #include <iomanip>
    
    class ScalarConverter {
    private:
        ScalarConverter();
        ScalarConverter(const ScalarConverter& other);
        ScalarConverter& operator=(const ScalarConverter& other);
        ~ScalarConverter();
    
    public:
        static void convert(const std::string& literal);
    };
    
    #endif
    
    // ScalarConverter.cpp
    #include "ScalarConverter.hpp"
    
    ScalarConverter::ScalarConverter() {}
    ScalarConverter::ScalarConverter(const ScalarConverter& other) { (void)other; }
    ScalarConverter& ScalarConverter::operator=(const ScalarConverter& other) {
        (void)other;
        return *this;
    }
    ScalarConverter::~ScalarConverter() {}
    
    static bool isChar(const std::string& literal) {
        return literal.length() == 1 && !std::isdigit(literal[0]);
    }
    
    static bool isInt(const std::string& literal) {
        size_t i = 0;
        if (literal[0] == '-' || literal[0] == '+')
            i++;
        for (; i < literal.length(); i++) {
            if (!std::isdigit(literal[i]))
                return false;
        }
        return literal.length() > 0 && (literal.length() > 1 || std::isdigit(literal[0]));
    }
    
    static bool isFloat(const std::string& literal) {
        if (literal == "-inff" || literal == "+inff" || literal == "nanf")
            return true;
        if (literal.length() < 2)
            return false;
        if (literal[literal.length() - 1] != 'f')
            return false;
    
        std::string withoutF = literal.substr(0, literal.length() - 1);
        size_t dotPos = withoutF.find('.');
        if (dotPos == std::string::npos)
            return false;
    
        size_t i = 0;
        if (withoutF[0] == '-' || withoutF[0] == '+')
            i++;
        bool hasDot = false;
        for (; i < withoutF.length(); i++) {
            if (withoutF[i] == '.') {
                if (hasDot) return false;
                hasDot = true;
            } else if (!std::isdigit(withoutF[i])) {
                return false;
            }
        }
        return hasDot;
    }
    
    static bool isDouble(const std::string& literal) {
        if (literal == "-inf" || literal == "+inf" || literal == "nan")
            return true;
    
        size_t dotPos = literal.find('.');
        if (dotPos == std::string::npos)
            return false;
    
        size_t i = 0;
        if (literal[0] == '-' || literal[0] == '+')
            i++;
        bool hasDot = false;
        for (; i < literal.length(); i++) {
            if (literal[i] == '.') {
                if (hasDot) return false;
                hasDot = true;
            } else if (!std::isdigit(literal[i])) {
                return false;
            }
        }
        return hasDot;
    }
    
    static void printChar(double value) {
        std::cout << "char: ";
        if (std::isnan(value) || std::isinf(value) ||
            value < 0 || value > 127) {
            std::cout << "impossible" << std::endl;
        } else if (!std::isprint(static_cast<int>(value))) {
            std::cout << "Non displayable" << std::endl;
        } else {
            std::cout << "'" << static_cast<char>(value) << "'" << std::endl;
        }
    }
    
    static void printInt(double value) {
        std::cout << "int: ";
        if (std::isnan(value) || std::isinf(value) ||
            value < std::numeric_limits<int>::min() ||
            value > std::numeric_limits<int>::max()) {
            std::cout << "impossible" << std::endl;
        } else {
            std::cout << static_cast<int>(value) << std::endl;
        }
    }
    
    static void printFloat(double value) {
        std::cout << "float: ";
        float f = static_cast<float>(value);
        std::cout << std::fixed << std::setprecision(1) << f << "f" << std::endl;
    }
    
    static void printDouble(double value) {
        std::cout << "double: ";
        std::cout << std::fixed << std::setprecision(1) << value << std::endl;
    }
    
    void ScalarConverter::convert(const std::string& literal) {
        double value;
    
        if (literal.empty()) {
            std::cerr << "Error: empty literal" << std::endl;
            return;
        }
    
        // 単一文字の場合
        if (isChar(literal)) {
            value = static_cast<double>(literal[0]);
        }
        // 整数の場合
        else if (isInt(literal)) {
            char* end;
            long l = std::strtol(literal.c_str(), &end, 10);
            if (*end != '\0' || l < std::numeric_limits<int>::min() ||
                l > std::numeric_limits<int>::max()) {
                std::cerr << "Error: invalid literal" << std::endl;
                return;
            }
            value = static_cast<double>(l);
        }
        // floatの場合
        else if (isFloat(literal)) {
            if (literal == "-inff")
                value = -std::numeric_limits<double>::infinity();
            else if (literal == "+inff")
                value = std::numeric_limits<double>::infinity();
            else if (literal == "nanf")
                value = std::numeric_limits<double>::quiet_NaN();
            else {
                std::string withoutF = literal.substr(0, literal.length() - 1);
                value = std::strtod(withoutF.c_str(), NULL);
            }
        }
        // doubleの場合
        else if (isDouble(literal)) {
            if (literal == "-inf")
                value = -std::numeric_limits<double>::infinity();
            else if (literal == "+inf")
                value = std::numeric_limits<double>::infinity();
            else if (literal == "nan")
                value = std::numeric_limits<double>::quiet_NaN();
            else
                value = std::strtod(literal.c_str(), NULL);
        }
        else {
            std::cerr << "Error: invalid literal" << std::endl;
            return;
        }
    
        printChar(value);
        printInt(value);
        printFloat(value);
        printDouble(value);
    }
    
    // main.cpp
    #include "ScalarConverter.hpp"
    
    int main(int argc, char** argv) {
        if (argc != 2) {
            std::cerr << "Usage: " << argv[0] << " <literal>" << std::endl;
            return 1;
        }
    
        ScalarConverter::convert(argv[1]);
        return 0;
    }
    

    1.3 テスト例

    ./convert 0
    # char: Non displayable
    # int: 0
    # float: 0.0f
    # double: 0.0
    
    ./convert 42
    # char: '*'
    # int: 42
    # float: 42.0f
    # double: 42.0
    
    ./convert 42.0f
    # char: '*'
    # int: 42
    # float: 42.0f
    # double: 42.0
    
    ./convert nan
    # char: impossible
    # int: impossible
    # float: nanf
    # double: nan
    
    ./convert a
    # char: 'a'
    # int: 97
    # float: 97.0f
    # double: 97.0
    

    ---

    2. ex01: Serialization

    2.1 課題の要件

  • Data構造体
  • serialize(): Data → uintptr_t
  • deserialize(): uintptr_t → Data
  • 2.2 Serializer

    // Data.hpp
    #ifndef DATA_HPP
    #define DATA_HPP
    
    #include <string>
    
    struct Data {
        int id;
        std::string name;
    };
    
    #endif
    
    // Serializer.hpp
    #ifndef SERIALIZER_HPP
    #define SERIALIZER_HPP
    
    #include <stdint.h>
    #include "Data.hpp"
    
    class Serializer {
    private:
        Serializer();
        Serializer(const Serializer& other);
        Serializer& operator=(const Serializer& other);
        ~Serializer();
    
    public:
        static uintptr_t serialize(Data* ptr);
        static Data* deserialize(uintptr_t raw);
    };
    
    #endif
    
    // Serializer.cpp
    #include "Serializer.hpp"
    
    Serializer::Serializer() {}
    Serializer::Serializer(const Serializer& other) { (void)other; }
    Serializer& Serializer::operator=(const Serializer& other) {
        (void)other;
        return *this;
    }
    Serializer::~Serializer() {}
    
    uintptr_t Serializer::serialize(Data* ptr) {
        return reinterpret_cast<uintptr_t>(ptr);
    }
    
    Data* Serializer::deserialize(uintptr_t raw) {
        return reinterpret_cast<Data*>(raw);
    }
    
    // main.cpp
    #include "Serializer.hpp"
    #include <iostream>
    
    int main() {
        Data original;
        original.id = 42;
        original.name = "Test Data";
    
        std::cout << "=== Original Data ===" << std::endl;
        std::cout << "Address: " << &original << std::endl;
        std::cout << "ID: " << original.id << std::endl;
        std::cout << "Name: " << original.name << std::endl;
    
        // シリアライズ
        uintptr_t serialized = Serializer::serialize(&original);
        std::cout << "\n=== Serialized ===" << std::endl;
        std::cout << "Raw value: " << serialized << std::endl;
    
        // デシリアライズ
        Data* deserialized = Serializer::deserialize(serialized);
        std::cout << "\n=== Deserialized ===" << std::endl;
        std::cout << "Address: " << deserialized << std::endl;
        std::cout << "ID: " << deserialized->id << std::endl;
        std::cout << "Name: " << deserialized->name << std::endl;
    
        // ポインタの比較
        std::cout << "\n=== Verification ===" << std::endl;
        if (deserialized == &original) {
            std::cout << "Success: Pointers are equal" << std::endl;
        } else {
            std::cout << "Failure: Pointers are different" << std::endl;
        }
    
        return 0;
    }
    

    ---

    3. ex02: Identify real type

    3.1 課題の要件

  • Base抽象クラス(仮想デストラクタのみ)
  • A, B, Cクラス(Baseを継承)
  • generate(): ランダムにA, B, Cのいずれかを生成
  • identify(Base* p): ポインタから型を識別
  • identify(Base& p): 参照から型を識別
  • 3.2 Base/A/B/Cクラス

    // Base.hpp
    #ifndef BASE_HPP
    #define BASE_HPP
    
    class Base {
    public:
        virtual ~Base();
    };
    
    #endif
    
    // Base.cpp
    #include "Base.hpp"
    
    Base::~Base() {}
    
    // A.hpp
    #ifndef A_HPP
    #define A_HPP
    
    #include "Base.hpp"
    
    class A : public Base {};
    
    #endif
    
    // B.hpp
    #ifndef B_HPP
    #define B_HPP
    
    #include "Base.hpp"
    
    class B : public Base {};
    
    #endif
    
    // C.hpp
    #ifndef C_HPP
    #define C_HPP
    
    #include "Base.hpp"
    
    class C : public Base {};
    
    #endif
    

    3.3 識別関数

    // functions.cpp
    #include "Base.hpp"
    #include "A.hpp"
    #include "B.hpp"
    #include "C.hpp"
    #include <cstdlib>
    #include <ctime>
    #include <iostream>
    
    Base* generate() {
        static bool seeded = false;
        if (!seeded) {
            std::srand(std::time(NULL));
            seeded = true;
        }
    
        int random = std::rand() % 3;
        switch (random) {
            case 0:
                std::cout << "Generated A" << std::endl;
                return new A();
            case 1:
                std::cout << "Generated B" << std::endl;
                return new B();
            default:
                std::cout << "Generated C" << std::endl;
                return new C();
        }
    }
    
    void identify(Base* p) {
        if (dynamic_cast<A*>(p)) {
            std::cout << "A" << std::endl;
        } else if (dynamic_cast<B*>(p)) {
            std::cout << "B" << std::endl;
        } else if (dynamic_cast<C*>(p)) {
            std::cout << "C" << std::endl;
        } else {
            std::cout << "Unknown" << std::endl;
        }
    }
    
    void identify(Base& p) {
        try {
            (void)dynamic_cast<A&>(p);
            std::cout << "A" << std::endl;
            return;
        } catch (std::bad_cast&) {}
    
        try {
            (void)dynamic_cast<B&>(p);
            std::cout << "B" << std::endl;
            return;
        } catch (std::bad_cast&) {}
    
        try {
            (void)dynamic_cast<C&>(p);
            std::cout << "C" << std::endl;
            return;
        } catch (std::bad_cast&) {}
    
        std::cout << "Unknown" << std::endl;
    }
    
    // main.cpp
    #include "Base.hpp"
    #include "A.hpp"
    #include "B.hpp"
    #include "C.hpp"
    #include <iostream>
    
    Base* generate();
    void identify(Base* p);
    void identify(Base& p);
    
    int main() {
        std::cout << "=== Test 1 ===" << std::endl;
        Base* ptr1 = generate();
        std::cout << "identify(ptr): ";
        identify(ptr1);
        std::cout << "identify(ref): ";
        identify(*ptr1);
        delete ptr1;
    
        std::cout << "\n=== Test 2 ===" << std::endl;
        Base* ptr2 = generate();
        std::cout << "identify(ptr): ";
        identify(ptr2);
        std::cout << "identify(ref): ";
        identify(*ptr2);
        delete ptr2;
    
        std::cout << "\n=== Test 3 ===" << std::endl;
        Base* ptr3 = generate();
        std::cout << "identify(ptr): ";
        identify(ptr3);
        std::cout << "identify(ref): ";
        identify(*ptr3);
        delete ptr3;
    
        // 直接生成してテスト
        std::cout << "\n=== Direct Creation Test ===" << std::endl;
        A a;
        B b;
        C c;
    
        std::cout << "A: ";
        identify(&a);
        std::cout << "B: ";
        identify(&b);
        std::cout << "C: ";
        identify(&c);
    
        return 0;
    }
    

    ---

    4. 重要な実装詳細

    4.1 uintptr_tについて

    uintptr_tは、ポインタを格納できる十分な大きさの符号なし整数型です:

    #include <stdint.h>  // or <cstdint> in C++11
    
    // 32ビットシステム: 4バイト
    // 64ビットシステム: 8バイト
    uintptr_t ptr_as_int = reinterpret_cast<uintptr_t>(some_pointer);
    

    4.2 dynamic_castの参照版

    ポインタ版とは異なり、参照版は失敗時にstd::bad_cast例外を投げます:

    // ポインタ版: 失敗時にNULLを返す
    A* a = dynamic_cast<A*>(base_ptr);
    if (a != NULL) { /* success */ }
    
    // 参照版: 失敗時に例外を投げる
    try {
        A& a = dynamic_cast<A&>(base_ref);
        /* success */
    } catch (std::bad_cast& e) {
        /* failure */
    }
    

    4.3 インスタンス化不可クラス

    42の課題では、ユーティリティクラスをインスタンス化不可にします:

    class Utility {
    private:
        Utility();  // private constructor
        Utility(const Utility&);
        Utility& operator=(const Utility&);
        ~Utility();
    
    public:
        static void doSomething();  // static methods only
    };
    
    // Utility util;  // Error: private constructor
    Utility::doSomething();  // OK
    

    ---

    まとめ

    本章で実装した内容:

  • ex00: static_castによるスカラー型変換
  • ex01: reinterpret_castによるシリアライズ
  • ex02: dynamic_castによる実行時型識別