CPP05 1.0
読み取り中…
検索中…
一致する文字列を見つけられません
C++ Module 05

この絵についての説明

1. 全体概要

1.1. 全体像と学習目標

CPP Module 05は、例外処理(Exception Handling)がオブジェクト指向プログラミング(OOP)の設計にどのように連携するかを学ぶことが目的です。単にtry-catchブロックを使うだけではなく、エラー状況をクラスとして表現して、継承を活用してエラーの種類を体系的に管理する手法を習得する。

主要な概念

  • カスタム例外クラス

std::exceptionを継承して、独自の例外クラスを作成する方法。

  • 例外の階層化

‍継承を用いて例外クラスを構造かし、catchブロックでポリモーフィズム(多態性)を活用する方法。

  • 抽象クラスとポリモーフィズム

‍共通のインターフェース(抽象クラス)を定義し、具体的な処理(実装)を派生クラスに任せることで、柔軟で拡張性の高い設計を実現する方法。

  • デザインパターン

‍特定の設計問題を解決するための再利用可能な設計パターン、特にこのモジュールでは ファクトリーパターン (Factory Pattern) に触れます。

  • リソース管理

‍動的に確保したメモリの解放など、例外が発生した場合でもリソースが適切に管理されることの重要性。

1.2. 課題の背景

このモジュールの課題は、「官僚制 (Bureaucracy)」というユニークなテーマで統一されています。これは、複雑で階層的なシステムをシミュレートするための優れたメタファーです。

  • 官僚 (Bureaucrat): システムの基本的な操作単位。
  • 書類 (Form): 処理されるべきタスクやデータ。
  • 署名(signForm)・実行(executeForm): タスクの承認プロセス。
  • 階級 (Grade): 操作を実行するための権限レベル。

この世界観において、「階級が足りない」「書類が署名されていない」といった問題は、プログラムにおける「エラー」や「不正な状態」に相当します。このような状況を例外として処理することで、エラーハンドリングをより具体的かつ直感的に学ぶことができます。

1.3. 学んだ内容の具体的な実用例

このモジュールで学ぶ概念は、現代のソフトウェア開発の多くの場面で不可欠です。

  • GUIアプリケーション: ユーザーが存在しないファイルを開こうとしたり、不正な形式のデータを入力したりした場合に、カスタム例外を投げて適切なエラーメッセージを表示する。
  • データベースシステム: データベースへの接続失敗、SQLクエリの実行エラー、トランザクションの競合などを、それぞれ異なる例外クラスとして定義し、状況に応じた後処理(ロールバックなど)を行う。
  • ゲーム開発: ゲームアセット(モデル、テクスチャ等)の読み込みに失敗した場合に AssetNotFoundException を投げたり、オンラインゲームでサーバーとの接続が切れた場合に NetworkException を投げたりする。
  • Webサーバー: HTTPリクエストの解析中に不正なリクエスト(例: 400 Bad Request)や、リソースが見つからない場合(例: 404 Not Found)を例外として処理する。
  • プラグインシステム: ファクトリーパターンを使い、実行時に設定ファイルから読み込んだ情報に基づいて、様々なプラグイン(AFormに相当)を動的に生成する。

2. 各課題の詳細 EX00~EX03

2.1. EX00: Mommy, when I grow up, I want to be a bureaucrat!

Files

Bureaucrat.hpp , Bureaucrat.cpp ,

main.cpp , Makefile

課題内容

Bureaucrat(官僚)クラスを作成します。このクラスは不変の名前と、1(最高)から150(最低)の範囲の階級 (_grade) を持ちます。コンストラクタや階級を変更するメソッドにおいて、階級が範囲外になった場合は GradeTooHighException または GradeTooLowException という例外を投げる必要があります。クラスはOrthodox Canonical Form(標準的な形式)に従う必要があります。

例外処理の実装
Bureaucrat::Bureaucrat(const std::string &name, int grade) : _name(name) {
if (grade < 1) {
}
if (grade > 150) {
}
this->_grade = grade;
std::cout << "Bureaucrat " << this->_name << " created with grade " << this->_grade << "." << std::endl;
}
T endl(T... args)
例外クラスの継承
// Bureaucrat.hpp
class Bureaucrat {
private:
public:
// Orthodox Canonical Form
// ... ommit.
// --- Exception Classes ---
// These inherit from std::exception to be part of the standard exception hierarchy.
// They override the what() method to provide a descriptive error message.
public:
virtual const char *what() const throw();
};
class GradeTooLowException : public std::exception {
public:
virtual const char *what() const throw();
};
};
// Bureaucrat.cpp
const char *Bureaucrat::GradeTooHighException::what() const throw() {
return "Bureaucrat Error: Grade is too high (must be >= 1)";
}
const char *Bureaucrat::GradeTooLowException::what() const throw() {
return "Bureaucrat Error: Grade is too low (must be <= 150)";
}
virtual const char * what() const
virtual const char * what() const
Represents a bureaucrat with a name and a grade.
Exception thrown when a bureaucrat's grade is too high.
Exception thrown when a bureaucrat's grade is too low.

学習ポイント

  • カスタム例外クラスの基本的な作成方法と throw の使い方。
  • std::exception を継承し、what() メソッドをオーバーライドする実践。
  • クラスの不変条件(この場合は階級の範囲)をコンストラクタとセッターで保証する設計。
  • Orthodox Canonical Form(コピーコンストラクタ、代入演算子など)の規約を遵守することの重要性。

動作結果

main.cpp
#include "Bureaucrat.hpp"
#include <iostream>
void print_header(const std::string &title) { std::cout << "\n\033[1;33m--- " << title << " ---\033[0m" << std::endl; }
int main() {
// --- Test 1: Valid Bureaucrat Creation ---
print_header("Test 1: Valid Bureaucrat Creation");
try {
Bureaucrat b1("Zaphod", 1);
std::cout << b1 << std::endl;
Bureaucrat b2("Arthur", 150);
std::cout << b2 << std::endl;
Bureaucrat b3("Ford", 42);
std::cout << b3 << std::endl;
} catch (const std::exception &e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
// --- Test 2: Grade Too High at Instantiation ---
print_header("Test 2: Grade Too High at Instantiation");
try {
Bureaucrat high("Highflyer", 0);
std::cout << high << std::endl;
} catch (const std::exception &e) {
std::cerr << "\033[1;31mCaught expected exception: " << e.what() << "\033[0m" << std::endl;
}
// --- Test 3: Grade Too Low at Instantiation ---
print_header("Test 3: Grade Too Low at Instantiation");
try {
Bureaucrat low("Lowballer", 151);
std::cout << low << std::endl;
} catch (const std::exception &e) {
std::cerr << "\033[1;31mCaught expected exception: " << e.what() << "\033[0m" << std::endl;
}
// --- Test 4: Grade Increment/Decrement ---
print_header("Test 4: Grade Increment/Decrement");
try {
Bureaucrat mid("Marvin", 75);
std::cout << "Initial: " << mid << std::endl;
mid.incrementGrade();
std::cout << "After increment: " << mid << std::endl;
mid.decrementGrade();
std::cout << "After decrement: " << mid << std::endl;
} catch (const std::exception &e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
// --- Test 5: Incrementing to the Limit ---
print_header("Test 5: Incrementing to the Limit");
try {
Bureaucrat top("Trillian", 2);
std::cout << "Initial: " << top << std::endl;
top.incrementGrade();
std::cout << "After increment: " << top << std::endl;
std::cout << "Attempting to increment again..." << std::endl;
top.incrementGrade(); // This should throw
} catch (const std::exception &e) {
std::cerr << "\033[1;31mCaught expected exception: " << e.what() << "\033[0m" << std::endl;
}
// --- Test 6: Decrementing to the Limit ---
print_header("Test 6: Decrementing to the Limit");
try {
Bureaucrat bottom("Slartibartfast", 149);
std::cout << "Initial: " << bottom << std::endl;
bottom.decrementGrade();
std::cout << "After decrement: " << bottom << std::endl;
std::cout << "Attempting to decrement again..." << std::endl;
bottom.decrementGrade(); // This should throw
} catch (const std::exception &e) {
std::cerr << "\033[1;31mCaught expected exception: " << e.what() << "\033[0m" << std::endl;
}
// --- Test 7: Copy and Assignment ---
print_header("Test 7: Copy and Assignment");
try {
Bureaucrat original("Original", 50);
std::cout << "Original: " << original << std::endl;
// Test copy constructor
Bureaucrat copy(original);
std::cout << "Copy: " << copy << std::endl;
Bureaucrat assigner("Assigner", 100);
std::cout << "Assigner: " << assigner << std::endl;
// Test assignment operator
// Note: The name 'Copy' will not change, only its grade.
copy = assigner;
std::cout << "Copy after assignment from Assigner: " << copy << std::endl;
} catch (const std::exception &e) {
std::cerr << "Caught unexpected exception: " << e.what() << std::endl;
}
print_header("All tests finished. Bureaucrats being destroyed now.");
return 0;
}
T copy(T... args)
void print_header(const std::string &title)
Prints a formatted header to the console.
Definition main.cpp:28
int main()
Main entry point of the program.
Definition main.cpp:34
T what(T... args)
出力
$ ./a.out
--- Test 1: Valid Bureaucrat Creation ---
Bureaucrat Zaphod created with grade 1.
Zaphod, bureaucrat grade 1.
Bureaucrat Arthur created with grade 150.
Arthur, bureaucrat grade 150.
Bureaucrat Ford created with grade 42.
Ford, bureaucrat grade 42.
Bureaucrat Ford has been destroyed.
Bureaucrat Arthur has been destroyed.
Bureaucrat Zaphod has been destroyed.
--- Test 2: Grade Too High at Instantiation ---
Caught expected exception: Bureaucrat Error: Grade is too high (must be >= 1)
--- Test 3: Grade Too Low at Instantiation ---
Caught expected exception: Bureaucrat Error: Grade is too low (must be <= 150)
--- Test 4: Grade Increment/Decrement ---
Bureaucrat Marvin created with grade 75.
Initial: Marvin, bureaucrat grade 75.
Bureaucrat Marvin grade incremented to 74.
After increment: Marvin, bureaucrat grade 74.
Bureaucrat Marvin grade decremented to 75.
After decrement: Marvin, bureaucrat grade 75.
Bureaucrat Marvin has been destroyed.
--- Test 5: Incrementing to the Limit ---
Bureaucrat Trillian created with grade 2.
Initial: Trillian, bureaucrat grade 2.
Bureaucrat Trillian grade incremented to 1.
After increment: Trillian, bureaucrat grade 1.
Attempting to increment again...
Bureaucrat Trillian has been destroyed.
Caught expected exception: Bureaucrat Error: Grade is too high (must be >= 1)
--- Test 6: Decrementing to the Limit ---
Bureaucrat Slartibartfast created with grade 149.
Initial: Slartibartfast, bureaucrat grade 149.
Bureaucrat Slartibartfast grade decremented to 150.
After decrement: Slartibartfast, bureaucrat grade 150.
Attempting to decrement again...
Bureaucrat Slartibartfast has been destroyed.
Caught expected exception: Bureaucrat Error: Grade is too low (must be <= 150)
--- Test 7: Copy and Assignment ---
Bureaucrat Original created with grade 50.
Original: Original, bureaucrat grade 50.
Bureaucrat Original has been copied.
Copy: Original, bureaucrat grade 50.
Bureaucrat Assigner created with grade 100.
Assigner: Assigner, bureaucrat grade 100.
Bureaucrat Original's grade has been assigned from Assigner.
Copy after assignment from Assigner: Original, bureaucrat grade 100.
Bureaucrat Assigner has been destroyed.
Bureaucrat Original has been destroyed.
Bureaucrat Original has been destroyed.
--- All tests finished. Bureaucrats being destroyed now. ---

2.2. EX01: Form up, maggots!

Files

Bureaucrat.hpp , Bureaucrat.cpp ,

Form.hpp , Form.cpp ,

main.cpp , Makefile

課題内容

Bureaucrat が署名するための Form(書類)クラスを作成します。 Form には、署名と実行に必要な階級が設定されています。 Bureaucrat クラスに Bureaucrat::signForm() メソッドを追加し、Form クラスの Form::beSigned() メソッドを呼び出します。官僚の階級がフォームの要求階級に満たない場合、Form 側が例外を投げます。(Form::GradeTooHighException::what(), Form::GradeTooLowException::what()

Bureaucrat::signForm()
try {
form.beSigned(*this);
std::cout << _name << " signed " << form.getName() << std::endl;
} catch (const std::exception &e) {
std::cout << _name << " couldn't sign " << form.getName() << " because " << e.what() << "." << std::endl;
}
}
void signForm(Form &form)
Attempts to sign a form. Calls the form's beSigned method and prints the result.
Represents a form that needs to be signed and executed.
Definition Form.hpp:32
void beSigned(const Bureaucrat &bureaucrat)
Sets the form's status to signed if the bureaucrat's grade is sufficient.
Definition Form.cpp:79
const std::string & getName() const
Definition Form.cpp:68
Form::beSigned()
void Form::beSigned(const Bureaucrat &bureaucrat) {
if (bureaucrat.getGrade() > _gradeToSign) {
}
_isSigned = true;
}
int getGrade() const
Gets the grade of the Bureaucrat.
Formの例外処理
const char *Form::GradeTooHighException::what() const throw() { return "Form grade is too high"; }
const char *Form::GradeTooLowException::what() const throw() { return "Form grade is too low"; }
virtual const char * what() const
Definition Form.cpp:91
virtual const char * what() const
Definition Form.cpp:97

学習ポイント

  • クラス間の相互作用(コラボレーション)の設計。

  • あるクラスのメソッド(Bureaucrat::signForm())が、別のクラスのメソッド(Form::beSigned())を呼び出し、その結果(例外)を処理する流れ。
  • 前方宣言 (class Form;) を使ったヘッダーファイル間の循環参照の回避。

動作結果

main.cpp
#include "Bureaucrat.hpp"
#include "Form.hpp"
#include <iostream>
void print_header(const std::string &title) { std::cout << "\n\033[1;33m--- " << title << " ---\033[0m" << std::endl; }
int main() {
// --- Test 1: Valid Form Creation ---
print_header("Test 1: Valid Form Creation");
try {
Form f1("A38", 50, 25);
std::cout << f1 << std::endl;
Form f2("B42-Alpha", 1, 1);
std::cout << f2 << std::endl;
} catch (const std::exception &e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
// --- Test 2: Invalid Form Creation (Grade Too High) ---
print_header("Test 2: Invalid Form Creation (Grade Too High)");
try {
Form invalid("C00L", 0, 10);
} catch (const std::exception &e) {
std::cerr << "\033[1;31mCaught expected exception: " << e.what() << "\033[0m" << std::endl;
}
// --- Test 3: Invalid Form Creation (Grade Too Low) ---
print_header("Test 3: Invalid Form Creation (Grade Too Low)");
try {
Form invalid("D-Negative", 10, 151);
} catch (const std::exception &e) {
std::cerr << "\033[1;31mCaught expected exception: " << e.what() << "\033[0m" << std::endl;
}
// --- Test 4: Bureaucrat signs Form successfully ---
print_header("Test 4: Successful Signing");
try {
Bureaucrat highPower("Zaphod", 10);
Form taxForm("27B/6", 20, 5);
std::cout << highPower << std::endl;
std::cout << taxForm << std::endl;
std::cout << "---" << std::endl;
highPower.signForm(taxForm);
std::cout << "---" << std::endl;
std::cout << taxForm << std::endl;
} catch (const std::exception &e) {
std::cerr << "Caught unexpected exception: " << e.what() << std::endl;
}
// --- Test 5: Bureaucrat fails to sign Form (Grade Too Low) ---
print_header("Test 5: Failed Signing (Grade Too Low)");
try {
Bureaucrat lowPower("Arthur", 140);
Form importantDoc("Deep Thought Access", 42, 1);
std::cout << lowPower << std::endl;
std::cout << importantDoc << std::endl;
std::cout << "---" << std::endl;
lowPower.signForm(importantDoc); // Should fail
std::cout << "---" << std::endl;
std::cout << importantDoc << std::endl;
} catch (const std::exception &e) {
std::cerr << "Caught unexpected exception: " << e.what() << std::endl;
}
// --- Test 6: Bureaucrat signs Form exactly at the required grade ---
print_header("Test 6: Signing at Exact Grade Requirement");
try {
Bureaucrat midManager("Ford", 75);
Form routineForm("Vogon Poetry Permit", 75, 50);
std::cout << midManager << std::endl;
std::cout << routineForm << std::endl;
std::cout << "---" << std::endl;
midManager.signForm(routineForm);
std::cout << "---" << std::endl;
std::cout << routineForm << std::endl;
} catch (const std::exception &e) {
std::cerr << "Caught unexpected exception: " << e.what() << std::endl;
}
print_header("All tests finished. Cleaning up.");
return 0;
}
Defines the Form class and its exceptions.
出力
$ ./a.out
--- Test 1: Valid Form Creation ---
Form A38 created.
Form 'A38':
Signed Status: Not Signed
Grade to Sign: 50
Grade to Execute: 25
Form B42-Alpha created.
Form 'B42-Alpha':
Signed Status: Not Signed
Grade to Sign: 1
Grade to Execute: 1
Form B42-Alpha destroyed.
Form A38 destroyed.
--- Test 2: Invalid Form Creation (Grade Too High) ---
Caught expected exception: Form grade is too high
--- Test 3: Invalid Form Creation (Grade Too Low) ---
Caught expected exception: Form grade is too low
--- Test 4: Successful Signing ---
Bureaucrat Zaphod created with grade 10.
Form 27B/6 created.
Zaphod, bureaucrat grade 10
Form '27B/6':
Signed Status: Not Signed
Grade to Sign: 20
Grade to Execute: 5
---
Zaphod signed 27B/6
---
Form '27B/6':
Signed Status: Signed
Grade to Sign: 20
Grade to Execute: 5
Form 27B/6 destroyed.
Bureaucrat Zaphod has been destroyed.
--- Test 5: Failed Signing (Grade Too Low) ---
Bureaucrat Arthur created with grade 140.
Form Deep Thought Access created.
Arthur, bureaucrat grade 140
Form 'Deep Thought Access':
Signed Status: Not Signed
Grade to Sign: 42
Grade to Execute: 1
---
Arthur couldn't sign Deep Thought Access because Form grade is too low.
---
Form 'Deep Thought Access':
Signed Status: Not Signed
Grade to Sign: 42
Grade to Execute: 1
Form Deep Thought Access destroyed.
Bureaucrat Arthur has been destroyed.
--- Test 6: Signing at Exact Grade Requirement ---
Bureaucrat Ford created with grade 75.
Form Vogon Poetry Permit created.
Ford, bureaucrat grade 75
Form 'Vogon Poetry Permit':
Signed Status: Not Signed
Grade to Sign: 75
Grade to Execute: 50
---
Ford signed Vogon Poetry Permit
---
Form 'Vogon Poetry Permit':
Signed Status: Signed
Grade to Sign: 75
Grade to Execute: 50
Form Vogon Poetry Permit destroyed.
Bureaucrat Ford has been destroyed.
--- All tests finished. Cleaning up. ---

2.3. EX02: No, you need form 28B, not 28C...

Files

Bureaucrat.hpp , Bureaucrat.cpp ,

AForm.hpp , AForm.cpp ,

ShrubberyCreationForm.hpp , ShrubberyCreationForm.cpp ,

RobotomyRequestForm.hpp , RobotomyRequestForm.cpp ,

PresidentialPardonForm.hpp , PresidentialPardonForm.cpp ,

main.cpp , Makefile

課題内容

Form を抽象基底クラス AForm に変更します。これは、純粋仮想関数(例: AForm::performAction() )を追加することで実現します。そして、具体的な3つのフォームクラス (ShrubberyCreationForm, RobotomyRequestForm, PresidentialPardonForm) を作成し、それぞれが固有のアクションを実装します。Bureaucrat にはフォームを実行するための Bureaucrat::executeForm() メソッドが追加されます。フォームの実行には「署名済みであること」と「実行に必要な階級を満たしていること」の2つの条件が必要です。

覚え書き
抽象基底クラス AForm : 申請フォームの元となるクラス
// AForm.hpp
#ifndef AFORM_HPP
#define AFORM_HPP
#include "Bureaucrat.hpp"
#include <iostream>
#include <stdexcept>
#include <string>
class Bureaucrat;
class AForm {
private:
const std::string _name;
bool _isSigned;
const int _gradeToSign;
const int _gradeToExecute;
const std::string _target;
AForm(); // Private default constructor
public:
// Orthodox Canonical Form
AForm(const std::string &name, int gradeToSign, int gradeToExecute, const std::string &target);
AForm(const AForm &other);
AForm &operator=(const AForm &other);
virtual ~AForm();
// Getters
const std::string &getName() const;
bool getIsSigned() const;
int getGradeToSign() const;
int getGradeToExecute() const;
const std::string &getTarget() const;
// Member functions
void beSigned(const Bureaucrat &bureaucrat);
void execute(Bureaucrat const &executor) const;
// Pure virtual function for concrete class action
virtual void performAction() const = 0;
// Exception Classes
public:
virtual const char *what() const throw();
};
class GradeTooLowException : public std::exception {
public:
virtual const char *what() const throw();
};
class FormNotSignedException : public std::exception {
public:
virtual const char *what() const throw();
};
};
std::ostream &operator<<(std::ostream &os, const AForm &form);
#endif
// AForm.cpp
// form.excecute() ... Excerpt in AForm.cpp
void AForm::execute(Bureaucrat const &executor) const {
if (!this->getIsSigned()) {
}
if (executor.getGrade() > this->getGradeToExecute()) {
}
this->performAction();
}
virtual const char * what() const
Definition AForm.cpp:72
An abstract base class for forms.
Definition AForm.hpp:34
virtual ~AForm()
Definition AForm.cpp:39
void execute(Bureaucrat const &executor) const
Central execution logic. Checks requirements before calling specific action.
Definition AForm.cpp:61
int getGradeToSign() const
Definition AForm.cpp:44
int getGradeToExecute() const
Definition AForm.cpp:45
bool getIsSigned() const
Definition AForm.cpp:43
const std::string & getName() const
Definition AForm.cpp:42
virtual void performAction() const =0
const std::string & getTarget() const
Definition AForm.cpp:46
void beSigned(const Bureaucrat &bureaucrat)
Definition AForm.cpp:48
AForm & operator=(const AForm &other)
Definition AForm.cpp:32
ShrubberyCreationForm : 低木を植えるための申請フォーム
// ShrubberyCreationForm.hpp
#ifndef SHRUBBERYCREATIONFORM_HPP
#define SHRUBBERYCREATIONFORM_HPP
#include "AForm.hpp"
#include <fstream>
class ShrubberyCreationForm : public AForm {
private:
public:
// Orthodox Canonical Form
// Action
virtual void performAction() const;
};
const char* const ASCII_ART[] = {\
" :.",
" -#*",
" -###*",
" -#####*",
" -+++++++#######*++++++=.",
" :*##################=.",
" .+#############*-",
" =############.",
" :-*###%%%%%####+:.",
" :--+%%%##*++*#%%%#--:.",
" :----##*+=-----==+*#+---:.",
" :-----==-----===-----==-----:.",
" :------------=======------------:.",
" :-------------=========-------------:.",
" :--------------============-------------:.",
" :--------------=====+++++=====--------------:.",
" :---------------======++++++======--------------:.",
" :---------------=========++++========----------------.",
"...............-========================:..............",
" -============++===========-.",
" :=============+++=============-.",
" -=============+++++==============:",
" :===============++++++==============-",
" -===============++++++++=====-=========:",
" :================++++++++++====+****======-",
" .-================++++++++++++===*****+-======:",
" ................:+++++++++++++=...-==:.........",
" =++++++++++++++:",
" -++++++++++++++++.",
" -++++++++++++++++++",
" :+++++++++++++++++++=",
" .+++++++++++++++++++++-",
" +++++++++++++++++++++++-",
" =++++++++++++++++++++++++:",
" -++++++++++++++++++++++++++.",
" -++++++++++++++++++===+++++++ =**##*- :#*##+-#####+",
" :+++++++++++++++++++====++++++= =%@@@@#: -@@@= -@@@@@#",
" .+++++++++++++++++++++++++++++++- -%@@@@*. -%= :@@@@@#",
" +++++++++++++++++++++++++++++++++- =%@@@@*. .*@@@@%-",
" =++++++++++++++++++++++++++++++++++: =%@@@@*: .*@@@@%=",
" -++++++++++++++++++++++++++++++++++++. -%@@@@+. .*@@@@%=",
" -++++++++++++++++++++++++++++++++++++++. #@@@@@%**#########. :@@@@@# .=",
" .............:------------.............. %@@@@@@@@@@@@@@@@@: -@@@@@# .+@#",
" -----------: *###########@@@@@@: -@@@@@#:*@@@#",
" -----------: %@@@@@. .-----::----:",
" -----------: %@@@@@.",
" *#####.",
" ",
" Merry Christmas! "
};
const int ASCII_ART_HEIGHT = sizeof(ASCII_ART) / sizeof(ASCII_ART[0]);
#endif
// ShrubberyCreationForm.cpp
#include "ShrubberyCreationForm.hpp"
ShrubberyCreationForm::ShrubberyCreationForm(const std::string &target)
: AForm("Shrubbery Creation", 145, 137, target) {}
ShrubberyCreationForm::ShrubberyCreationForm(const ShrubberyCreationForm &other) : AForm(other) {}
return *this;
}
std::string filename = getTarget() + "_shrubbery";
std::ofstream outfile(filename.c_str());
if (!outfile.is_open()) {
std::cerr << "Error: Could not open file " << filename << std::endl;
return;
}
// out each line of ASCII art
for (int i = 0; i < ASCII_ART_HEIGHT; ++i) {
outfile << ASCII_ART[i] << std::endl;
}
outfile.close();
std::cout << "Created shrubbery file at " << filename << std::endl;
}
T c_str(T... args)
A concrete form that creates a file with ASCII trees.
ShrubberyCreationForm & operator=(const ShrubberyCreationForm &other)
virtual void performAction() const
Creates a file named "_target" + "_shrubbery" and writes ASCII trees to it.
const char *const ASCII_ART[]
const int ASCII_ART_HEIGHT
const char *const ASCII_ART[]
const int ASCII_ART_HEIGHT
詳細

RobotomyRequestForm : ロボットを作るための申請フォーム
// RobotomyRequestForm.hpp
#ifndef ROBOTOMYREQUESTFORM_HPP
#define ROBOTOMYREQUESTFORM_HPP
#include "AForm.hpp"
#include <cstdlib> // For rand()
class RobotomyRequestForm : public AForm {
private:
public:
// Orthodox Canonical Form
// Action
virtual void performAction() const;
};
#endif
// RobotomyRequestForm.cpp
#include "RobotomyRequestForm.hpp"
RobotomyRequestForm::RobotomyRequestForm(const std::string &target) : AForm("Robotomy Request", 72, 45, target) {}
RobotomyRequestForm::RobotomyRequestForm(const RobotomyRequestForm &other) : AForm(other) {}
return *this;
}
std::cout << "* VRRR... BZZZZT... CLANK! *" << std::endl;
if (rand() % 2) {
std::cout << getTarget() << " has been successfully robotomized!" << std::endl;
} else {
std::cout << "The robotomy on " << getTarget() << " has failed." << std::endl;
}
}
A concrete form that simulates a robotomy attempt.
RobotomyRequestForm & operator=(const RobotomyRequestForm &other)
virtual void performAction() const
Makes drilling noises and informs of a 50% successful robotomy.
T rand(T... args)
詳細

PresidentialPardonForm : ザフォード銀河大統領によって恩赦(刑罰の減刑)されるための申請フォーム
// Bureaucrat.cpp
void Bureaucrat::executeForm(AForm const &form) {
try {
form.execute(*this);
std::cout << _name << " executed " << form.getName() << std::endl;
} catch (const std::exception &e) {
std::cout << _name << " couldn't execute " << form.getName() << " because " << e.what() << "." << std::endl;
}
}
void executeForm(AForm const &form)
Attempts to execute a form. Calls the form's execute method and prints the result.
詳細

学習ポイント

  • ポリモーフィズム(多態性): AForm * 型のポインタで、どの具体的なフォームオブジェクトも同じように扱えること。
  • 抽象クラス: 共通のインターフェースを定義し、具体的な実装を派生クラスに強制する設計。
  • ロジックの共通化: 実行条件のチェックなど、すべての派生クラスで共通のロジックを基底クラス(AForm::execute())にまとめることで、コードの重複を避け、保守性を高めるエレガントな設計。

動作結果

main.cpp
#include "AForm.hpp"
#include "Bureaucrat.hpp"
#include "PresidentialPardonForm.hpp"
#include "RobotomyRequestForm.hpp"
#include "ShrubberyCreationForm.hpp"
#include <cstdlib> // For srand
#include <ctime> // For time
#include <iostream>
void print_header(const std::string &title) { std::cout << "\n\033[1;33m--- " << title << " ---\033[0m" << std::endl; }
int main() {
// Seed the random number generator once for RobotomyRequestForm
// Create bureaucrats
Bureaucrat high("Zaphod", 1);
Bureaucrat mid("Ford", 50);
Bureaucrat low("Arthur", 148);
// Create forms
ShrubberyCreationForm shrub("garden");
RobotomyRequestForm robo("Bender");
PresidentialPardonForm pardon("Marvin");
print_header("Initial Status");
std::cout << high << std::endl;
std::cout << mid << std::endl;
std::cout << low << std::endl;
std::cout << shrub << std::endl;
std::cout << robo << std::endl;
std::cout << pardon << std::endl;
// --- Test Case 1: Low-level Bureaucrat ---
print_header("Test Case 1: Low-level Bureaucrat 'Arthur' (Grade 148)");
low.signForm(shrub); // Fails (needs 145)
low.signForm(robo); // Fails
low.signForm(pardon); // Fails
low.executeForm(shrub); // Fails (not signed)
// --- Test Case 2: Signing Shrubbery and executing ---
print_header("Test Case 2: Signing & Executing Shrubbery Form");
Bureaucrat shrubExpert("Shrub-Master", 140);
std::cout << shrubExpert << std::endl;
shrubExpert.signForm(shrub);
shrubExpert.executeForm(shrub); // Fails (exec grade 137 needed)
std::cout << "---" << std::endl;
mid.executeForm(shrub); // Mid-level can execute it
// --- Test Case 3: Signing and Executing Robotomy ---
print_header("Test Case 3: Signing & Executing Robotomy Form");
mid.signForm(robo);
mid.executeForm(robo); // Fails (exec grade 45 needed)
std::cout << "---" << std::endl;
high.executeForm(robo); // High-level can execute it
// --- Test Case 4: Signing and Executing Presidential Pardon ---
print_header("Test Case 4: Signing & Executing Presidential Pardon");
mid.signForm(pardon); // Fails (sign grade 25 needed)
std::cout << "---" << std::endl;
high.signForm(pardon);
high.executeForm(pardon);
// --- Test Case 5: Executing a form that isn't signed ---
print_header("Test Case 5: Executing an unsigned form");
RobotomyRequestForm robo2("WALL-E");
high.executeForm(robo2);
print_header("Tests complete.");
return 0;
}
A concrete form that grants a presidential pardon.
T srand(T... args)
T time(T... args)
出力
$ ./a.out
--- Initial Status ---
Zaphod, bureaucrat grade 1
Ford, bureaucrat grade 50
Arthur, bureaucrat grade 148
Form 'Shrubbery Creation' (Target: garden)
- Signed Status: Not Signed
- Grade to Sign: 145
- Grade to Execute: 137
Form 'Robotomy Request' (Target: Bender)
- Signed Status: Not Signed
- Grade to Sign: 72
- Grade to Execute: 45
Form 'Presidential Pardon' (Target: Marvin)
- Signed Status: Not Signed
- Grade to Sign: 25
- Grade to Execute: 5
--- Test Case 1: Low-level Bureaucrat 'Arthur' (Grade 148) ---
Arthur couldn't sign Shrubbery Creation because Grade is too low.
Arthur couldn't sign Robotomy Request because Grade is too low.
Arthur couldn't sign Presidential Pardon because Grade is too low.
Arthur couldn't execute Shrubbery Creation because Form is not signed.
--- Test Case 2: Signing & Executing Shrubbery Form ---
Shrub-Master, bureaucrat grade 140
Shrub-Master signed Shrubbery Creation
Shrub-Master couldn't execute Shrubbery Creation because Grade is too low.
---
Created shrubbery file at garden_shrubbery
Ford executed Shrubbery Creation
--- Test Case 3: Signing & Executing Robotomy Form ---
Ford signed Robotomy Request
Ford couldn't execute Robotomy Request because Grade is too low.
---
* VRRR... BZZZZT... CLANK! *
The robotomy on Bender has failed.
Zaphod executed Robotomy Request
--- Test Case 4: Signing & Executing Presidential Pardon ---
Ford couldn't sign Presidential Pardon because Grade is too low.
---
Zaphod signed Presidential Pardon
Marvin has been pardoned by Zaphod Beeblebrox.
Zaphod executed Presidential Pardon
--- Test Case 5: Executing an unsigned form ---
Zaphod couldn't execute Robotomy Request because Form is not signed.
--- Tests complete. ---
$ cat garden_shrubbery
:.
-#*
-###*
-#####*
-+++++++#######*++++++=.
:*##################=.
.+#############*-
=############.
:-*###%%%%%####+:.
:--+%%%##*++*#%%%#--:.
:----##*+=-----==+*#+---:.
:-----==-----===-----==-----:.
:------------=======------------:.
:-------------=========-------------:.
:--------------============-------------:.
:--------------=====+++++=====--------------:.
:---------------======++++++======--------------:.
:---------------=========++++========----------------.
...............-========================:..............
-============++===========-.
:=============+++=============-.
-=============+++++==============:
:===============++++++==============-
-===============++++++++=====-=========:
:================++++++++++====+****======-
.-================++++++++++++===*****+-======:
................:+++++++++++++=...-==:.........
=++++++++++++++:
-++++++++++++++++.
-++++++++++++++++++
:+++++++++++++++++++=
.+++++++++++++++++++++-
+++++++++++++++++++++++-
=++++++++++++++++++++++++:
-++++++++++++++++++++++++++.
-++++++++++++++++++===+++++++ =**##*- :#*##+-#####+
:+++++++++++++++++++====++++++= =%@@@@#: -@@@= -@@@@@#
.+++++++++++++++++++++++++++++++- -%@@@@*. -%= :@@@@@#
+++++++++++++++++++++++++++++++++- =%@@@@*. .*@@@@%-
=++++++++++++++++++++++++++++++++++: =%@@@@*: .*@@@@%=
-++++++++++++++++++++++++++++++++++++. -%@@@@+. .*@@@@%=
-++++++++++++++++++++++++++++++++++++++. #@@@@@%**#########. :@@@@@# .=
.............:------------.............. %@@@@@@@@@@@@@@@@@: -@@@@@# .+@#
-----------: *###########@@@@@@: -@@@@@#:*@@@#
-----------: %@@@@@. .-----::----:
-----------: %@@@@@.
*#####.
Merry Christmas!
$

2.4. EX03: At least this beats coffee-making

Files

Bureaucrat.hpp , Bureaucrat.cpp ,

AForm.hpp , AForm.cpp ,

ShrubberyCreationForm.hpp , ShrubberyCreationForm.cpp ,

RobotomyRequestForm.hpp , RobotomyRequestForm.cpp ,

PresidentialPardonForm.hpp , PresidentialPardonForm.cpp ,

Intern.hpp , Intern.cpp , IFormFactory.hpp , ControllableFailingFactory.hpp , ControllableFailingFactory.cpp ,

main.cpp , Makefile

課題内容

Intern(インターン)クラスを作成します。このクラスは Intern::makeForm(const std::string &formName, const std::string &formTarget) という1つのメソッドを持ちます。このメソッドは、フォーム名(formName文字列)に応じて対応する具体的なフォームオブジェクトを動的に生成し、AForm * 型のポインタとして返します。if-else if のような煩雑な実装は避け、より洗練された方法で実装することが求められます。

覚え書き
実装したクラス
  • Intern : 申請フォームのオブジェクトを生成するためのクラス(ファクトリーパターン)
  • IFormFactory : 「フォームを生成する」という機能の共通ルールを抽象基底クラス
  • ControllableFailingFactory : テスト環境用のモック。意図的にnewの失敗(std::bad_alloc)をシミュレートできる偽物の実装

Step 1: Intern クラスの導入(ファクトリーパターン)

‍当初、 Intern はフォーム名(文字列 formName)を受け取り、それに対応する具体的なフォームオブジェクトを new して返す役割を担いました。これは、オブジェクトの生成ロジックを Intern クラスにカプセル化するファクトリーパターンの基本的な実装です。

Step 2: 依存性の注入(DI)によるリファクタリング

newが失敗するエッジケースをテストするために、より高度な設計である 依存性の注入(Dependency Injection) パターンを導入しました。

‍1. インターフェースの作成 (IFormFactory): まず、「フォームを生成する」という機能の共通ルールを抽象基底クラスとして定義しました。

IFormFactory 抽象基底クラス: すべてのFactoryが従うべき「契約」です。
// *** IFormFactory.hpp ***
#ifndef IFORMFACTORY_HPP
#define IFORMFACTORY_HPP
//#include ...
// Form Generator Interface.
class IFormFactory {
public:
virtual ~IFormFactory() {}
// Pure virtual functions to generate three different forms.
virtual AForm *createShrubbery(const std::string &target) const = 0;
virtual AForm *createRobotomy(const std::string &target) const = 0;
virtual AForm *createPresidential(const std::string &target) const = 0;
};
#endif
virtual AForm * createPresidential(const std::string &target) const =0
virtual AForm * createShrubbery(const std::string &target) const =0
virtual ~IFormFactory()
virtual AForm * createRobotomy(const std::string &target) const =0

2. Internの修正: Internが直接newするのではなく、外部から渡されたIFormFactoryインターフェースを通じてフォームを生成するように変更しました。

Intern.hpp : DefaultFormFactoryの宣言と、Internクラス内に例外クラスの宣言を追加します。
#ifndef INTERN_HPP
#define INTERN_HPP
#include "AForm.hpp"
#include "IFormFactory.hpp"
#include "PresidentialPardonForm.hpp"
#include "RobotomyRequestForm.hpp"
#include "ShrubberyCreationForm.hpp"
#include <string>
// --- Default Factory for production ---
public:
virtual AForm *createShrubbery(const std::string &target) const;
virtual AForm *createRobotomy(const std::string &target) const;
virtual AForm *createPresidential(const std::string &target) const;
};
class Intern {
private:
IFormFactory *_factory;
bool _ownsFactory;
public:
// Orthodox Canonical Form
Intern(IFormFactory *factory);
Intern();
// Member function
AForm *makeForm(const std::string &formName, const std::string &formTarget);
// Exception for unknown form types
public:
virtual const char *what() const throw();
};
private:
Intern(const Intern &other);
Intern &operator=(const Intern &other);
};
#endif
virtual AForm * createPresidential(const std::string &target) const
Definition Intern.cpp:20
virtual AForm * createShrubbery(const std::string &target) const
Definition Intern.cpp:16
virtual AForm * createRobotomy(const std::string &target) const
Definition Intern.cpp:19
virtual const char * what() const
Definition Intern.cpp:68
AForm * makeForm(const std::string &formName, const std::string &formTarget)
Definition Intern.cpp:45
~Intern()
Definition Intern.cpp:37
Intern()
Definition Intern.cpp:33
Exception thrown when an unknown form name is requested.
Intern.cpp
#include "Intern.hpp"
// --- Default Factory for production ---
return new ShrubberyCreationForm(target);
}
AForm *DefaultFormFactory::createRobotomy(const std::string &target) const { return new RobotomyRequestForm(target); }
return new PresidentialPardonForm(target);
}
// --- Intern ---
// --- Orthodox Canonical Form ---
Intern::Intern(IFormFactory *factory) : _factory(factory), _ownsFactory(false) {
if (!_factory) {
this->_factory = new DefaultFormFactory();
this->_ownsFactory = true;
}
}
Intern::Intern() : _factory(new DefaultFormFactory()), _ownsFactory(true) {
std::cout << "A wild Intern appears!" << std::endl;
}
if (_ownsFactory) {
delete _factory;
}
std::cout << "The Intern has vanished, probably to get coffee." << std::endl;
}
// --- Intern method ---
AForm *Intern::makeForm(const std::string &formName, const std::string &formTarget) {
struct FormMap {
AForm *(IFormFactory::*creator)(const std::string &) const;
};
const FormMap formMap[] = {{"shrubbery creation", &IFormFactory::createShrubbery},
{"robotomy request", &IFormFactory::createRobotomy},
{"presidential pardon", &IFormFactory::createPresidential}};
const int numForms = sizeof(formMap) / sizeof(formMap[0]);
for (int i = 0; i < numForms; ++i) {
if (formName == formMap[i].name) {
std::cout << "Intern (via factory) creates " << formName << std::endl;
return (this->_factory->*(formMap[i].creator))(formTarget);
}
}
}
const char *Intern::UnknownFormException::what() const throw() {
return "Error: Unknown form name provided. Cannot create form.";
}
Defines the Intern class, which can create forms.
ちなみに、リファクタリング前の Intern::makeForm()
// makeForm() ... Excerpt in Intern.cpp
AForm *Intern::makeForm(const std::string &formName, const std::string &formTarget) {
std::string formNames[] = {"shrubbery creation", "robotomy request", "presidential pardon"};
AForm *createdForm = NULL;
int formIndex = -1;
for (int i = 0; i < 3; ++i) {
if (formName == formNames[i]) {
formIndex = i;
break;
}
}
switch (formIndex) {
case 0:
createdForm = new ShrubberyCreationForm(formTarget);
break;
case 1:
createdForm = new RobotomyRequestForm(formTarget);
break;
case 2:
createdForm = new PresidentialPardonForm(formTarget);
break;
default:
// If no match was found, throw an exception
}
std::cout << "Intern creates " << formName << std::endl;
return createdForm;
}

3. 実装クラスの作成:

DefaultFormFactory : 本番環境で使われる、実際にnewを行う標準的な実装。
// *** Define ... Intern.hpp ***
public:
virtual AForm *createShrubbery(const std::string &target) const;
virtual AForm *createRobotomy(const std::string &target) const;
virtual AForm *createPresidential(const std::string &target) const;
};
// *** Impliment ... Intern.cpp ***
return new ShrubberyCreationForm(target);
}
AForm *DefaultFormFactory::createRobotomy(const std::string &target) const { return new RobotomyRequestForm(target); }
return new PresidentialPardonForm(target);
}
ControllableFailingFactory : テスト環境で使う、意図的にnewの失敗(std::bad_alloc)をシミュレートできる偽物の実装。
// *** Define ... ControllableFailingFactory.hpp ***
private:
static int failCounter;
public:
static void setFailAfter(int count);
void checkFailure() const;
virtual AForm *createShrubbery(const std::string &target) const;
virtual AForm *createRobotomy(const std::string &target) const;
virtual AForm *createPresidential(const std::string &target) const;
};
// *** Impliment ... ControllableFailingFactory.cpp ***
// Defines and initializes the entity of a static member variable.
int ControllableFailingFactory::failCounter = -1;
void ControllableFailingFactory::setFailAfter(int count) { failCounter = count; }
if (failCounter == 0) {
failCounter = -1;
throw std::bad_alloc();
}
if (failCounter > 0) {
failCounter--;
}
}
return new ShrubberyCreationForm(target);
}
return new RobotomyRequestForm(target);
}
return new PresidentialPardonForm(target);
}
virtual AForm * createRobotomy(const std::string &target) const
virtual AForm * createPresidential(const std::string &target) const
virtual AForm * createShrubbery(const std::string &target) const

4. テストコード

‍テストファイルは、新しく作成したControllableFailingFactory.hppをインクルードするだけで

intern_di_test.cpp : 新しく作成したControllableFailingFactory.hppをインクルードするだけで、テストコードが非常にすっきりします。
#include "ControllableFailingFactory.hpp" // 分離したテスト用Factoryをインクルード
#include "Intern.hpp"
#include "gtest/gtest.h"
TEST(InternDependencyInjectionTest, HandlesAllocationFailure) {
Intern intern(&factory);
// 0回目の呼び出し(つまり、次)で失敗するように設定
EXPECT_THROW(intern.makeForm("robotomy request", "Bender"), std::bad_alloc);
}
TEST(InternDependencyInjectionTest, FailsOnSecondAllocation) {
Intern intern(&factory);
// 1回成功した後、2回目で失敗するように設定
AForm *form1 = NULL;
ASSERT_NO_THROW(form1 = intern.makeForm("shrubbery creation", "garden"));
ASSERT_NE(form1, (AForm *)NULL);
EXPECT_THROW(intern.makeForm("robotomy request", "Bender"), std::bad_alloc);
delete form1;
}

このリファクタリングにより、Internクラス自体のコードを変更することなく、テスト時にその振る舞いを柔軟に切り替えることが可能になりました。

学習のポイント

Point 1: ファクトリーパターンと依存性の注入(DI)

‍この課題の核心は、テスト容易性を向上させるために、単純なファクトリーパターンからDIパターンへと設計を進化させるプロセスにあります。

工夫した点:

if-else, ifswitch 文の羅列を避けるため、フォーム名とそれを生成するメンバ関数ポインタをペアにした配列( FormMap )を用意しました。これにより、ループ処理で一致するフォームを探し、動的に対応する生成メソッドを呼び出す、という宣言的でスケーラブルな実装になりました。

FormMap を使った実装
struct FormMap {
AForm *(IFormFactory::*creator)(const std::string &) const;
};
const FormMap formMap[] = {{"shrubbery creation", &IFormFactory::createShrubbery},
{"robotomy request", &IFormFactory::createRobotomy},
{"presidential pardon", &IFormFactory::createPresidential}};
const int numForms = sizeof(formMap) / sizeof(formMap[0]);
for (int i = 0; i < numForms; ++i) {
if (formName == formMap[i].name) {
std::cout << "Intern (via factory) creates " << formName << std::endl;
return (this->_factory->*(formMap[i].creator))(formTarget);
}
}

注意事項:

‍DIを導入したことで、 Intern クラスは IFormFactory のインスタンスをポインタで保持することになりました。このポインタが指すメモリの 所有権(ownership) を誰が持つのか(誰が delete する責任を持つのか)を明確にする必要があり、そのために _ownsFactory というフラグを導入しました。これは、C++における手動メモリ管理の難しさと重要性を示す良い例です。

Intern
// *** member of Intern ... Excerpt in Intern.hpp ***
class Intern {
private:
IFormFactory *_factory;
bool _ownsFactory;
// ... omit
}
// *** Management resource ... Excerpt in Intern.cpp ***
Intern::Intern(IFormFactory *factory) : _factory(factory), _ownsFactory(false) {
if (!_factory) {
this->_factory = new DefaultFormFactory();
this->_ownsFactory = true;
}
}
Intern::Intern() : _factory(new DefaultFormFactory()), _ownsFactory(true) {
std::cout << "A wild Intern appears!" << std::endl;
}
if (_ownsFactory) {
delete _factory;
}
std::cout << "The Intern has vanished, probably to get coffee." << std::endl;
}

Point 2: コピー禁止クラスの設計

Internクラスは、内部にポインタ(_factory)を持つため、安易なコピー(シャローコピー)は 二重解放(Double Free) などの致命的なエラーを引き起こします。

気をつけたこと:

‍この問題を根本的に解決するため、コピーコンストラクタとコピー代入演算子をprivateセクションに宣言し、実装は提供しないというC++98の古典的なイディオムを用いて、クラスを コピー禁止(Non-copyable) にしました。これにより、誤ったコピー操作をコンパイルエラーまたはリンクエラーの段階で防ぐことができます。

Intern Non-copyable
class Intern {
public:
// ...(omit)
private:
Intern(const Intern &other);
Intern &operator=(const Intern &other);
};

Point 3: テスト容易性を意識した設計

‍「newの失敗をどうやってテストするか?」という問いが、今回の高度なリファクタリングの出発点でした。

学んだこと:

‍ある機能(Intern)が、別の機能(フォームのnew)に直接依存している状態(密結合)は、テストを困難にします。

間にインターフェース(IFormFactory)を挟むことで、依存関係を逆転させ(依存性逆転の原則)、テスト時に偽物のオブジェクト(モック)を「注入」できるようになります。

このように、テストのしやすさを考えることが、結果的により柔軟で保守性の高いソフトウェア設計につながるという、テスト駆動開発(TDD)の重要な思想を実践的に学ぶことができました。

動作結果

main.cpp
// #include ...(ommit)
void print_header(const std::string &title) { std::cout << "\n\033[1;33m--- " << title << " ---\033[0m" << std::endl; }
int main() {
// Seed the random number generator
srand(time(NULL));
// Create an Intern and a high-level Bureaucrat to test the forms
Intern someRandomIntern;
Bureaucrat boss("Zaphod", 1);
AForm *form;
// --- Test 1: Create a Robotomy Request Form ---
print_header("Test 1: Create Robotomy Request");
try {
form = someRandomIntern.makeForm("robotomy request", "Bender");
if (form) {
std::cout << boss << std::endl;
std::cout << *form << std::endl;
boss.signForm(*form);
boss.executeForm(*form);
delete form; // Clean up the allocated memory
}
} catch (const std::exception &e) {
}
// --- Test 2: Create a Shrubbery Creation Form ---
print_header("Test 2: Create Shrubbery Creation");
try {
form = someRandomIntern.makeForm("shrubbery creation", "home");
if (form) {
std::cout << boss << std::endl;
std::cout << *form << std::endl;
boss.signForm(*form);
boss.executeForm(*form);
delete form;
}
} catch (const std::exception &e) {
}
// --- Test 3: Create a Presidential Pardon Form ---
print_header("Test 3: Create Presidential Pardon");
try {
form = someRandomIntern.makeForm("presidential pardon", "Ford Prefect");
if (form) {
std::cout << boss << std::endl;
std::cout << *form << std::endl;
boss.signForm(*form);
boss.executeForm(*form);
delete form;
}
} catch (const std::exception &e) {
}
// --- Test 4: Attempt to create an unknown form ---
print_header("Test 4: Create Unknown Form");
try {
form = someRandomIntern.makeForm("lunch request", "the kitchen");
if (form) { // This block should not be reached
std::cout << "This should not print." << std::endl;
delete form;
}
} catch (const std::exception &e) {
std::cerr << "\033[1;31mCaught expected exception: " << e.what() << "\033[0m" << std::endl;
}
print_header("All tests complete.");
return 0;
}
出力
$ ./a.out
A wild Intern appears!
--- Test 1: Create Robotomy Request ---
Intern (via factory) creates robotomy request
Zaphod, bureaucrat grade 1
Form 'Robotomy Request' (Target: Bender)
- Signed Status: Not Signed
- Grade to Sign: 72
- Grade to Execute: 45
Zaphod signed Robotomy Request
* VRRR... BZZZZT... CLANK! *
Bender has been successfully robotomized!
Zaphod executed Robotomy Request
--- Test 2: Create Shrubbery Creation ---
Intern (via factory) creates shrubbery creation
Zaphod, bureaucrat grade 1
Form 'Shrubbery Creation' (Target: home)
- Signed Status: Not Signed
- Grade to Sign: 145
- Grade to Execute: 137
Zaphod signed Shrubbery Creation
Created shrubbery file at home_shrubbery
Zaphod executed Shrubbery Creation
--- Test 3: Create Presidential Pardon ---
Intern (via factory) creates presidential pardon
Zaphod, bureaucrat grade 1
Form 'Presidential Pardon' (Target: Ford Prefect)
- Signed Status: Not Signed
- Grade to Sign: 25
- Grade to Execute: 5
Zaphod signed Presidential Pardon
Ford Prefect has been pardoned by Zaphod Beeblebrox.
Zaphod executed Presidential Pardon
--- Test 4: Create Unknown Form ---
Caught expected exception: Error: Unknown form name provided. Cannot create form.
--- All tests complete. ---
$

3. レビューに向けて

インタラクティブ学習ガイド

概要図 リファクタ前

InternAFormを作る -> BureaucratAFormに署名 or 実行する

概要図 リファクタ後

InternAFormを作る -> BureaucratAFormに署名 or 実行する

3.1. TL;DR

これまでに学んできたこと。

  • 抽象クラス(AForm): CPP Module 04

    ‍「書類」の基本ルールだけを決めた設計図。AFromだけではクラスは使えないが、継承クラスたちの「具体的な書類」は必ずルールに従う必要がある。

    * ポリモーフィズム

    ‍「書類」の型(AForm *)を使って実際の具体的な書類(「植樹申請」、「ロボット化申請」)など、書類が違うものを同じように扱える便利な仕組み。

新しく学ぶこと。

  • 例外処理とは?

    ‍プログラムのエラーハンドリングのコードをif文で細かくチェックをする代わりに、エラー処理の場所にジャンプさせる仕組み。

    ‍C++ では例外処理はtry - catch, 例外発生はthrow

    Pythonだとtry - except, raise

    * ファクトリーパターン(Intern)のクラス実装

    ‍「書類を作る専門家」です。どんな書類が欲しいか名前を伝えるだけで、適切な書類を作るクラスです。

    多くのif文(ifの森)を使ったコードは読みにくくメンテナンス性が悪いので、それを避けた実装方法を学びます。

3.2. Requirements Checklist : 要件チェックリスト

カテゴリ チェック項目 OK/NG コメント
共通 OCF(Orthdox Canonical Form)のクラス設計
Makefile(ビルド、fcleanでオブジェクトと実行ファイルの削除)
ヘッダーのインクルードガード?
メモリリークが発生しないか?
EX00 Bureaucratの階級が範囲外のとき、正しい例外を投げるか?
EX01 Bureaucratの階級不足でsignFormが失敗するか?
EX02 AFormは抽象クラスになっていて、インスタンス化できないか?
フォームが未署名の場合、executeは例外を投げるか?
Bureaucratの階級不足でexecuteが失敗するか?
各フォームのアクション(ファイル作成、コンソール出力)は正しいか?
EX03 Internif-else ifの羅列を避けた実装になっているか?
Internは存在しないフォーム名を指定されたときにエラーを出すか?
Internがnewしたメモリは、main側でdeleteされているか?

3.3. 実際に議論した内容

  • throw() 例外仕様の意義と仮想関数のオーバーライドについて議論しました。 👉 レビューノート
  • EX00~03までの共通コードにも関わらず細かな違い(コメントやdoxygen用コードブロックなど)がありました。 diff コマンドによる確認。
  • 依存性の注入(DI)と依存性逆転の原則について、EX03で採用したことについて説明しました。 👉 説明ページ
  • git cherry-pick の機能を紹介。👉説明ページ

‍再提出時に review ブランチを作っていらないファイルを削除する作業で役立つ(過去のコミット操作を参照して同じ操作が行えるため)

3.4. 想定質問

  • たくさんのif(tree-of-if:ifの森)を避けるための設計はどう考えたか?

InternmakeForm()では、フォーム名とインデックスを対応させた配列とswitch文で実装。

Intern.cpp makeForm
AForm *Intern::makeForm(const std::string &formName, const std::string &formTarget) {
std::string formNames[] = {"shrubbery creation", "robotomy request", "presidential pardon"};
AForm *createdForm = NULL;
int formIndex = -1;
for (int i = 0; i < 3; ++i) {
if (formName == formNames[i]) {
formIndex = i;
break;
}
}
switch (formIndex) {
case 0:
createdForm = new ShrubberyCreationForm(formTarget);
break;
case 1:
createdForm = new RobotomyRequestForm(formTarget);
break;
case 2:
createdForm = new PresidentialPardonForm(formTarget);
break;
default:
// If no match was found, throw an exception
}
std::cout << "Intern creates " << formName << std::endl;
return createdForm;
}

余談:C言語の課題ではswitch文が使用許可されていなかったため、関数ポインターを使った実装をよくしていましたが、可読性を優先してこちらの方法は不採用。

  • 例外処理の設計について

‍「AFormexecuteメソッドで、署名と権限の共通チェックを行ってから、各派生クラスのperformActionを呼び出す設計にしました。これにより、チェック処理のコード重複を防いでいます。」

  • constの正確性について

‍「引数やメソッドがconstであるべき箇所に注意を払いました。特に、実行するだけのexecuteメソッドや、情報を取得するgetterconstにしています。」

  • メモリリーク

‍AFormのexecuteメソッドでエラーを発生したときに、正しくリソース解放されるか?

要確認 !

  • cpp05/ex02 のRobotomyRequestFormクラスの動作について、C言語の関数を使わずにみんなどうやって作っているんだろう??

‍実装したいこと50の確率で成功したことを出力、それ以外は失敗したことを出力する

懸念事項:<cstdlib>, <ctime> はC言語の関数をC++で使えるように取り込むようにしているため、std::rand()std::time()も使えないのでは??

例えば、program_state.txtのようなファイルにtrue / false を実行の度に上書きするような処理を書けばいけるが、そこまで必要??


4. Project Links

  • Intra - CPP05 : 提出リポジトリ
  • GitHub - CPP05 (非公開) : 開発リポジトリ
  • My Documents : CPP05 / CPP_Modules

    ‍My Documents は Doxygenで作成されたソースコードドキュメントです。

    クラスの連携図やソースコードの説明に関する情報がまとめられています。

    * GitHub - cpp_module_tester : CPP00~09のテストコード(Google Testを使用)

5. Other

  • cppreference.com

    ‍C++の標準ライブラリに含まれているクラスやメソッドに関するマニュアルが記載されています。

    DoxygenのTAGFILESにURLとdocs/cppreference-doxygen-web.tag.xmlを定義しているため、Doxygenで作成されたページはcppreference.comに存在するエントリーはリンクされます。

  • ブランチの使い方

    main: 安定版

    feature/ex00: 各課題の機能開発を行う。

    docs: ここにpushすると、GitHub Actions workflowが動作してドキュメントが公開されます。

  • ドキュメントページ

    ‍C++ Moduleのプロジェクト毎にDoxygenを使ったドキュメントをまとめています。

    doxygen用のcssテーマは、Doxygen Awesomeを使用。