CPP05 1.0
|
CPP Module 05は、例外処理(Exception Handling)がオブジェクト指向プログラミング(OOP)の設計にどのように連携するかを学ぶことが目的です。単にtry-catch
ブロックを使うだけではなく、エラー状況をクラスとして表現して、継承を活用してエラーの種類を体系的に管理する手法を習得する。
主要な概念
std::exception
を継承して、独自の例外クラスを作成する方法。
継承を用いて例外クラスを構造かし、
catch
ブロックでポリモーフィズム(多態性)を活用する方法。
共通のインターフェース(抽象クラス)を定義し、具体的な処理(実装)を派生クラスに任せることで、柔軟で拡張性の高い設計を実現する方法。
特定の設計問題を解決するための再利用可能な設計パターン、特にこのモジュールでは ファクトリーパターン (Factory Pattern) に触れます。
動的に確保したメモリの解放など、例外が発生した場合でもリソースが適切に管理されることの重要性。
このモジュールの課題は、「官僚制 (Bureaucracy)」というユニークなテーマで統一されています。これは、複雑で階層的なシステムをシミュレートするための優れたメタファーです。
この世界観において、「階級が足りない」「書類が署名されていない」といった問題は、プログラムにおける「エラー」や「不正な状態」に相当します。このような状況を例外として処理することで、エラーハンドリングをより具体的かつ直感的に学ぶことができます。
このモジュールで学ぶ概念は、現代のソフトウェア開発の多くの場面で不可欠です。
AssetNotFoundException
を投げたり、オンラインゲームでサーバーとの接続が切れた場合に NetworkException
を投げたりする。400 Bad Request
)や、リソースが見つからない場合(例: 404 Not Found
)を例外として処理する。AForm
に相当)を動的に生成する。
Bureaucrat
(官僚)クラスを作成します。このクラスは不変の名前と、1(最高)から150(最低)の範囲の階級 (_grade
) を持ちます。コンストラクタや階級を変更するメソッドにおいて、階級が範囲外になった場合はGradeTooHighException
またはGradeTooLowException
という例外を投げる必要があります。クラスはOrthodox Canonical Form(標準的な形式)に従う必要があります。例外処理の実装
if (grade < 1) {throw Bureaucrat::GradeTooHighException();}if (grade > 150) {throw Bureaucrat::GradeTooLowException();}this->_grade = grade;std::cout << "Bureaucrat " << this->_name << " created with grade " << this->_grade << "." << std::endl;}T endl(T... args)例外クラスの継承
// Bureaucrat.hppclass 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:};class GradeTooLowException : public std::exception {public:};};// Bureaucrat.cppreturn "Bureaucrat Error: Grade is too high (must be >= 1)";}return "Bureaucrat Error: Grade is too low (must be <= 150)";}virtual const char * what() constDefinition Bureaucrat.cpp:113virtual const char * what() constDefinition Bureaucrat.cpp:121Exception 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);Bureaucrat b2("Arthur", 150);Bureaucrat b3("Ford", 42);}// --- Test 2: Grade Too High at Instantiation ---print_header("Test 2: Grade Too High at Instantiation");try {Bureaucrat high("Highflyer", 0);}// --- Test 3: Grade Too Low at Instantiation ---print_header("Test 3: Grade Too Low at Instantiation");try {Bureaucrat low("Lowballer", 151);}// --- Test 4: Grade Increment/Decrement ---print_header("Test 4: Grade Increment/Decrement");try {Bureaucrat mid("Marvin", 75);mid.incrementGrade();mid.decrementGrade();}// --- Test 5: Incrementing to the Limit ---print_header("Test 5: Incrementing to the Limit");try {Bureaucrat top("Trillian", 2);top.incrementGrade();top.incrementGrade(); // This should throw}// --- Test 6: Decrementing to the Limit ---print_header("Test 6: Decrementing to the Limit");try {Bureaucrat bottom("Slartibartfast", 149);bottom.decrementGrade();bottom.decrementGrade(); // This should throw}// --- Test 7: Copy and Assignment ---print_header("Test 7: Copy and Assignment");try {Bureaucrat original("Original", 50);// Test copy constructorBureaucrat copy(original);Bureaucrat assigner("Assigner", 100);// Test assignment operator// Note: The name 'Copy' will not change, only its grade.copy = assigner;}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:28T 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. ---
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 << " 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.Definition Bureaucrat.cpp:68void beSigned(const Bureaucrat &bureaucrat)Sets the form's status to signed if the bureaucrat's grade is sufficient.Definition Form.cpp:79Form::beSigned()
if (bureaucrat.getGrade() > _gradeToSign) {throw Form::GradeTooLowException();}_isSigned = true;}Definition Form.hpp:63Formの例外処理
- クラス間の相互作用(コラボレーション)の設計。
![]()
- あるクラスのメソッド(
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);Form f2("B42-Alpha", 1, 1);}// --- Test 2: Invalid Form Creation (Grade Too High) ---print_header("Test 2: Invalid Form Creation (Grade Too High)");try {Form invalid("C00L", 0, 10);}// --- 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);}// --- Test 4: Bureaucrat signs Form successfully ---print_header("Test 4: Successful Signing");try {Bureaucrat highPower("Zaphod", 10);Form taxForm("27B/6", 20, 5);highPower.signForm(taxForm);}// --- 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);lowPower.signForm(importantDoc); // Should fail}// --- 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);midManager.signForm(routineForm);}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 SignedGrade to Sign: 50Grade to Execute: 25Form B42-Alpha created.Form 'B42-Alpha':Signed Status: Not SignedGrade to Sign: 1Grade to Execute: 1Form 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 10Form '27B/6':Signed Status: Not SignedGrade to Sign: 20Grade to Execute: 5---Zaphod signed 27B/6---Form '27B/6':Signed Status: SignedGrade to Sign: 20Grade to Execute: 5Form 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 140Form 'Deep Thought Access':Signed Status: Not SignedGrade to Sign: 42Grade to Execute: 1---Arthur couldn't sign Deep Thought Access because Form grade is too low.---Form 'Deep Thought Access':Signed Status: Not SignedGrade to Sign: 42Grade to Execute: 1Form 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 75Form 'Vogon Poetry Permit':Signed Status: Not SignedGrade to Sign: 75Grade to Execute: 50---Ford signed Vogon Poetry Permit---Form 'Vogon Poetry Permit':Signed Status: SignedGrade to Sign: 75Grade to Execute: 50Form Vogon Poetry Permit destroyed.Bureaucrat Ford has been destroyed.--- All tests finished. Cleaning up. ---
Bureaucrat.hpp , Bureaucrat.cpp ,
ShrubberyCreationForm.hpp , ShrubberyCreationForm.cpp ,
RobotomyRequestForm.hpp , RobotomyRequestForm.cpp ,
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 constructorpublic:// Orthodox Canonical Formvirtual ~AForm();// Getters// Member functions// Pure virtual function for concrete class action// Exception Classespublic:};class GradeTooLowException : public std::exception {public:};class FormNotSignedException : public std::exception {public:};};#endif// AForm.cpp// form.excecute() ... Excerpt in AForm.cppif (!this->getIsSigned()) {throw AForm::FormNotSignedException();}if (executor.getGrade() > this->getGradeToExecute()) {throw AForm::GradeTooLowException();}this->performAction();}Definition AForm.hpp:74Definition AForm.hpp:70void execute(Bureaucrat const &executor) constCentral execution logic. Checks requirements before calling specific action.Definition AForm.cpp:61virtual void performAction() const =0ShrubberyCreationForm : 低木を植えるための申請フォーム
// ShrubberyCreationForm.hpp#ifndef SHRUBBERYCREATIONFORM_HPP#define SHRUBBERYCREATIONFORM_HPP#include "AForm.hpp"#include <fstream>private:public:// Orthodox Canonical Form// Action};" :."," -#*"," -###*"," -#####*"," -+++++++#######*++++++=."," :*##################=."," .+#############*-"," =############."," :-*###%%%%%####+:."," :--+%%%##*++*#%%%#--:."," :----##*+=-----==+*#+---:."," :-----==-----===-----==-----:."," :------------=======------------:."," :-------------=========-------------:."," :--------------============-------------:."," :--------------=====+++++=====--------------:."," :---------------======++++++======--------------:."," :---------------=========++++========----------------.","...............-========================:.............."," -============++===========-."," :=============+++=============-."," -=============+++++==============:"," :===============++++++==============-"," -===============++++++++=====-=========:"," :================++++++++++====+****======-"," .-================++++++++++++===*****+-======:"," ................:+++++++++++++=...-==:........."," =++++++++++++++:"," -++++++++++++++++."," -++++++++++++++++++"," :+++++++++++++++++++="," .+++++++++++++++++++++-"," +++++++++++++++++++++++-"," =++++++++++++++++++++++++:"," -++++++++++++++++++++++++++."," -++++++++++++++++++===+++++++ =**##*- :#*##+-#####+"," :+++++++++++++++++++====++++++= =%@@@@#: -@@@= -@@@@@#"," .+++++++++++++++++++++++++++++++- -%@@@@*. -%= :@@@@@#"," +++++++++++++++++++++++++++++++++- =%@@@@*. .*@@@@%-"," =++++++++++++++++++++++++++++++++++: =%@@@@*: .*@@@@%="," -++++++++++++++++++++++++++++++++++++. -%@@@@+. .*@@@@%="," -++++++++++++++++++++++++++++++++++++++. #@@@@@%**#########. :@@@@@# .="," .............:------------.............. %@@@@@@@@@@@@@@@@@: -@@@@@# .+@#"," -----------: *###########@@@@@@: -@@@@@#:*@@@#"," -----------: %@@@@@. .-----::----:"," -----------: %@@@@@."," *#####."," "," Merry Christmas! "};#endif// ShrubberyCreationForm.cpp#include "ShrubberyCreationForm.hpp"ShrubberyCreationForm::ShrubberyCreationForm(const std::string &target): AForm("Shrubbery Creation", 145, 137, target) {}AForm::operator=(other);return *this;}std::ofstream outfile(filename.c_str());if (!outfile.is_open()) {return;}// out each line of ASCII art}outfile.close();}T c_str(T... args)A concrete form that creates a file with ASCII trees.Definition ShrubberyCreationForm.hpp:28ShrubberyCreationForm & operator=(const ShrubberyCreationForm &other)Definition ShrubberyCreationForm.cpp:25virtual void performAction() constCreates a file named "_target" + "_shrubbery" and writes ASCII trees to it.Definition ShrubberyCreationForm.cpp:35~ShrubberyCreationForm()Definition ShrubberyCreationForm.cpp:30詳細
RobotomyRequestForm : ロボットを作るための申請フォーム
// RobotomyRequestForm.hpp#ifndef ROBOTOMYREQUESTFORM_HPP#define ROBOTOMYREQUESTFORM_HPP#include "AForm.hpp"#include <cstdlib> // For rand()private:public:// Orthodox Canonical Form// Action};#endif// RobotomyRequestForm.cpp#include "RobotomyRequestForm.hpp"RobotomyRequestForm::RobotomyRequestForm(const std::string &target) : AForm("Robotomy Request", 72, 45, target) {}AForm::operator=(other);return *this;}if (rand() % 2) {} else {}}A concrete form that simulates a robotomy attempt.Definition RobotomyRequestForm.hpp:28RobotomyRequestForm & operator=(const RobotomyRequestForm &other)Definition RobotomyRequestForm.cpp:24~RobotomyRequestForm()Definition RobotomyRequestForm.cpp:29virtual void performAction() constMakes drilling noises and informs of a 50% successful robotomy.Definition RobotomyRequestForm.cpp:34T rand(T... args)詳細
PresidentialPardonForm : ザフォード銀河大統領によって恩赦(刑罰の減刑)されるための申請フォーム
// Bureaucrat.cpptry {form.execute(*this);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.Definition Bureaucrat.cpp:70詳細
- ポリモーフィズム(多態性):
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 RobotomyRequestFormstd::srand(std::time(NULL));// Create bureaucratsBureaucrat high("Zaphod", 1);Bureaucrat mid("Ford", 50);Bureaucrat low("Arthur", 148);// Create formsShrubberyCreationForm shrub("garden");RobotomyRequestForm robo("Bender");PresidentialPardonForm pardon("Marvin");print_header("Initial Status");// --- 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); // Failslow.signForm(pardon); // Failslow.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);shrubExpert.signForm(shrub);shrubExpert.executeForm(shrub); // Fails (exec grade 137 needed)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)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)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.Definition PresidentialPardonForm.hpp:27T srand(T... args)T time(T... args)出力
$ ./a.out--- Initial Status ---Zaphod, bureaucrat grade 1Ford, bureaucrat grade 50Arthur, bureaucrat grade 148Form 'Shrubbery Creation' (Target: garden)- Signed Status: Not Signed- Grade to Sign: 145- Grade to Execute: 137Form 'Robotomy Request' (Target: Bender)- Signed Status: Not Signed- Grade to Sign: 72- Grade to Execute: 45Form '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 140Shrub-Master signed Shrubbery CreationShrub-Master couldn't execute Shrubbery Creation because Grade is too low.---Created shrubbery file at garden_shrubberyFord executed Shrubbery Creation--- Test Case 3: Signing & Executing Robotomy Form ---Ford signed Robotomy RequestFord 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 PardonMarvin 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!$
Bureaucrat.hpp , Bureaucrat.cpp ,
ShrubberyCreationForm.hpp , ShrubberyCreationForm.cpp ,
RobotomyRequestForm.hpp , RobotomyRequestForm.cpp ,
PresidentialPardonForm.hpp , PresidentialPardonForm.cpp ,
Intern.hpp , Intern.cpp , IFormFactory.hpp , ControllableFailingFactory.hpp , ControllableFailingFactory.cpp ,
Intern
(インターン)クラスを作成します。このクラスはIntern::makeForm(const std::string &formName, const std::string &formTarget)
という1つのメソッドを持ちます。このメソッドは、フォーム名(formName
文字列)に応じて対応する具体的なフォームオブジェクトを動的に生成し、AForm *
型のポインタとして返します。if-else if
のような煩雑な実装は避け、より洗練された方法で実装することが求められます。
- 覚え書き
- 実装したクラス
- Intern : 申請フォームのオブジェクトを生成するためのクラス(ファクトリーパターン)
- IFormFactory : 「フォームを生成する」という機能の共通ルールを抽象基底クラス
- ControllableFailingFactory : テスト環境用のモック。意図的にnewの失敗(
std::bad_alloc
)をシミュレートできる偽物の実装
当初、
Intern
はフォーム名(文字列formName
)を受け取り、それに対応する具体的なフォームオブジェクトをnew
して返す役割を担いました。これは、オブジェクトの生成ロジックをIntern
クラスにカプセル化するファクトリーパターンの基本的な実装です。
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.};#endifDefinition IFormFactory.hpp:20virtual AForm * createPresidential(const std::string &target) const =0virtual AForm * createShrubbery(const std::string &target) const =0virtual AForm * createRobotomy(const std::string &target) const =02.
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:};class Intern {private:IFormFactory *_factory;bool _ownsFactory;public:// Orthodox Canonical FormIntern(IFormFactory *factory);Intern();~Intern();// Member function// Exception for unknown form typespublic:};private:};#endifDefinition Intern.hpp:29virtual AForm * createPresidential(const std::string &target) constDefinition Intern.cpp:20virtual AForm * createShrubbery(const std::string &target) constDefinition Intern.cpp:16virtual AForm * createRobotomy(const std::string &target) constDefinition Intern.cpp:19Definition Intern.hpp:36AForm * makeForm(const std::string &formName, const std::string &formTarget)Definition Intern.cpp:45Exception thrown when an unknown form name is requested.Intern.cpp
#include "Intern.hpp"// --- Default Factory for production ---}AForm *DefaultFormFactory::createRobotomy(const std::string &target) const { return new RobotomyRequestForm(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) {}Intern::~Intern() {if (_ownsFactory) {delete _factory;}}// --- Intern method ---struct FormMap {std::string name;};{"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) {return (this->_factory->*(formMap[i].creator))(formTarget);}}throw Intern::UnknownFormException();}return "Error: Unknown form name provided. Cannot create form.";}Defines the Intern class, which can create forms.Definition Intern.hpp:51ちなみに、リファクタリング前の
Intern::makeForm()
// makeForm() ... Excerpt in Intern.cppAForm *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 exceptionthrow Intern::UnknownFormException();}return createdForm;}3. 実装クラスの作成:
DefaultFormFactory : 本番環境で使われる、実際に
new
を行う標準的な実装。// *** Define ... Intern.hpp ***public:};// *** Impliment ... Intern.cpp ***}AForm *DefaultFormFactory::createRobotomy(const std::string &target) const { return new RobotomyRequestForm(target); }}ControllableFailingFactory : テスト環境で使う、意図的に
new
の失敗(std::bad_alloc
)をシミュレートできる偽物の実装。// *** Define ... ControllableFailingFactory.hpp ***private:static int failCounter;public:};// *** Impliment ... ControllableFailingFactory.cpp ***// Defines and initializes the entity of a static member variable.int ControllableFailingFactory::failCounter = -1;if (failCounter == 0) {failCounter = -1;throw std::bad_alloc();}if (failCounter > 0) {failCounter--;}}checkFailure();}checkFailure();}checkFailure();}static void setFailAfter(int count)Definition ControllableFailingFactory.cpp:25virtual AForm * createRobotomy(const std::string &target) constDefinition ControllableFailingFactory.cpp:41virtual AForm * createPresidential(const std::string &target) constDefinition ControllableFailingFactory.cpp:45void checkFailure() constDefinition ControllableFailingFactory.cpp:27virtual AForm * createShrubbery(const std::string &target) constDefinition ControllableFailingFactory.cpp:374. テストコード
テストファイルは、新しく作成したControllableFailingFactory.hppをインクルードするだけで
intern_di_test.cpp : 新しく作成した
ControllableFailingFactory.hpp
をインクルードするだけで、テストコードが非常にすっきりします。#include "ControllableFailingFactory.hpp" // 分離したテスト用Factoryをインクルード#include "Intern.hpp"#include "gtest/gtest.h"TEST(InternDependencyInjectionTest, HandlesAllocationFailure) {ControllableFailingFactory factory;Intern intern(&factory);// 0回目の呼び出し(つまり、次)で失敗するように設定}TEST(InternDependencyInjectionTest, FailsOnSecondAllocation) {ControllableFailingFactory factory;Intern intern(&factory);// 1回成功した後、2回目で失敗するように設定AForm *form1 = NULL;ASSERT_NO_THROW(form1 = intern.makeForm("shrubbery creation", "garden"));ASSERT_NE(form1, (AForm *)NULL);delete form1;}このリファクタリングにより、
Intern
クラス自体のコードを変更することなく、テスト時にその振る舞いを柔軟に切り替えることが可能になりました。
この課題の核心は、テスト容易性を向上させるために、単純なファクトリーパターンからDIパターンへと設計を進化させるプロセスにあります。
工夫した点:
if-else
,if
やswitch
文の羅列を避けるため、フォーム名とそれを生成するメンバ関数ポインタをペアにした配列(FormMap
)を用意しました。これにより、ループ処理で一致するフォームを探し、動的に対応する生成メソッドを呼び出す、という宣言的でスケーラブルな実装になりました。FormMap を使った実装
struct FormMap {std::string name;};{"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) {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) {}Intern::~Intern() {if (_ownsFactory) {delete _factory;}}
Intern
クラスは、内部にポインタ(_factory
)を持つため、安易なコピー(シャローコピー)は 二重解放(Double Free) などの致命的なエラーを引き起こします。気をつけたこと:
この問題を根本的に解決するため、コピーコンストラクタとコピー代入演算子を
private
セクションに宣言し、実装は提供しないというC++98の古典的なイディオムを用いて、クラスを コピー禁止(Non-copyable) にしました。これにより、誤ったコピー操作をコンパイルエラーまたはリンクエラーの段階で防ぐことができます。
「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 generatorsrand(time(NULL));// Create an Intern and a high-level Bureaucrat to test the formsIntern someRandomIntern;Bureaucrat boss("Zaphod", 1);AForm *form;// --- Test 1: Create a Robotomy Request Form ---print_header("Test 1: Create Robotomy Request");try {if (form) {boss.signForm(*form);boss.executeForm(*form);delete form; // Clean up the allocated memory}}// --- Test 2: Create a Shrubbery Creation Form ---print_header("Test 2: Create Shrubbery Creation");try {if (form) {boss.signForm(*form);boss.executeForm(*form);delete form;}}// --- Test 3: Create a Presidential Pardon Form ---print_header("Test 3: Create Presidential Pardon");try {if (form) {boss.signForm(*form);boss.executeForm(*form);delete form;}}// --- Test 4: Attempt to create an unknown form ---print_header("Test 4: Create Unknown Form");try {if (form) { // This block should not be reacheddelete form;}}print_header("All tests complete.");return 0;}出力
$ ./a.outA wild Intern appears!--- Test 1: Create Robotomy Request ---Intern (via factory) creates robotomy requestZaphod, bureaucrat grade 1Form 'Robotomy Request' (Target: Bender)- Signed Status: Not Signed- Grade to Sign: 72- Grade to Execute: 45Zaphod 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 creationZaphod, bureaucrat grade 1Form 'Shrubbery Creation' (Target: home)- Signed Status: Not Signed- Grade to Sign: 145- Grade to Execute: 137Zaphod signed Shrubbery CreationCreated shrubbery file at home_shrubberyZaphod executed Shrubbery Creation--- Test 3: Create Presidential Pardon ---Intern (via factory) creates presidential pardonZaphod, bureaucrat grade 1Form 'Presidential Pardon' (Target: Ford Prefect)- Signed Status: Not Signed- Grade to Sign: 25- Grade to Execute: 5Zaphod signed Presidential PardonFord 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. ---$
これまでに学んできたこと。
新しく学ぶこと。
- 例外処理とは?
* ファクトリーパターン(プログラムのエラーハンドリングのコードを
if
文で細かくチェックをする代わりに、エラー処理の場所にジャンプさせる仕組み。C++ では例外処理は
try - catch
, 例外発生はthrow
Pythonだと
try - except
,raise
Intern
)のクラス実装「書類を作る専門家」です。どんな書類が欲しいか名前を伝えるだけで、適切な書類を作るクラスです。
多くの
if
文(ifの森)を使ったコードは読みにくくメンテナンス性が悪いので、それを避けた実装方法を学びます。
カテゴリ | チェック項目 | OK/NG | コメント |
---|---|---|---|
共通 | OCF(Orthdox Canonical Form)のクラス設計 Makefile(ビルド、fcleanでオブジェクトと実行ファイルの削除) ヘッダーのインクルードガード? メモリリークが発生しないか? | ||
EX00 | Bureaucrat の階級が範囲外のとき、正しい例外を投げるか? | ||
EX01 | Bureaucrat の階級不足でsignForm が失敗するか? | ||
EX02 | AForm は抽象クラスになっていて、インスタンス化できないか? フォームが未署名の場合、executeは例外を投げるか? Bureaucrat の階級不足でexecuteが失敗するか? 各フォームのアクション(ファイル作成、コンソール出力)は正しいか? | ||
EX03 | Intern はif-else if の羅列を避けた実装になっているか? Intern は存在しないフォーム名を指定されたときにエラーを出すか? Internがnewしたメモリは、main側でdeleteされているか? |
throw()
例外仕様の意義と仮想関数のオーバーライドについて議論しました。 👉 レビューノートdiff
コマンドによる確認。git cherry-pick
の機能を紹介。👉説明ページ再提出時に
review
ブランチを作っていらないファイルを削除する作業で役立つ(過去のコミット操作を参照して同じ操作が行えるため)
if
(tree-of-if
:ifの森)を避けるための設計はどう考えたか?
Intern
でmakeForm()
では、フォーム名とインデックスを対応させた配列とswitch
文で実装。Intern.cpp makeForm
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 exceptionthrow Intern::UnknownFormException();}return createdForm;}余談:C言語の課題では
switch
文が使用許可されていなかったため、関数ポインターを使った実装をよくしていましたが、可読性を優先してこちらの方法は不採用。
「
AForm
のexecute
メソッドで、署名と権限の共通チェックを行ってから、各派生クラスのperformAction
を呼び出す設計にしました。これにより、チェック処理のコード重複を防いでいます。」
「引数やメソッドが
const
であるべき箇所に注意を払いました。特に、実行するだけのexecute
メソッドや、情報を取得するgetter
はconst
にしています。」
AFormのexecuteメソッドでエラーを発生したときに、正しくリソース解放されるか?
要確認 !
RobotomyRequestForm
クラスの動作について、C言語の関数を使わずにみんなどうやって作っているんだろう??実装したいこと
50の確率で成功したことを出力、それ以外は失敗したことを出力する
懸念事項:
<cstdlib>, <ctime>
はC言語の関数をC++で使えるように取り込むようにしているため、std::rand()
やstd::time()
も使えないのでは??例えば、
program_state.txt
のようなファイルにtrue
/false
を実行の度に上書きするような処理を書けばいけるが、そこまで必要??
* GitHub - cpp_module_tester : CPP00~09のテストコード(Google Testを使用)My Documents は Doxygenで作成されたソースコードドキュメントです。
クラスの連携図やソースコードの説明に関する情報がまとめられています。
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を使用。