第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 課題の要件
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 課題の要件
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
---
まとめ
本章で実装した内容: