Forward declarations help manage circular dependencies between classes:
// In Form.hpp
class Bureaucrat; // Forward declaration
// In Bureaucrat.hpp
class Form; // Forward declaration
- Allows header files to reference each other
- Reduces compilation dependencies
- Helps prevent circular inclusion issues
When two classes need to know about each other:
// Class A needs Class B
class A {
B* b; // Needs forward declaration of B
};
// Class B needs Class A
class B {
A* a; // Needs forward declaration of A
};
Solutions:
- Forward declarations
- Proper header organization
- Minimal includes in header files
Using const correctly in class design:
class Form {
private:
const std::string _name; // Constant member
const int _gradeToSign; // Constant member
public:
std::string getName() const; // Const member function
};
Benefits:
- Prevents accidental modifications
- Clarifies intent
- Enables optimization
Proper initialization of constant members:
Form::Form(const std::string& name, int grade)
: _name(name), // Initialize const member
_gradeToSign(grade) // Initialize const member
{
// Constructor body
}
Importance:
- Required for const members
- More efficient than assignment
- Guarantees initialization order
Three levels of access:
class Form {
private:
// Internal implementation details
const std::string _name;
protected:
// Available to derived classes
bool _isSigned;
public:
// Public interface
void beSigned(const Bureaucrat& b);
};
Different levels of guarantee:
-
Basic Guarantee:
- No resource leaks
- Program in valid state
-
Strong Guarantee:
- Operation succeeds or no effect
- No side effects if exception thrown
-
No-throw Guarantee:
- Operation never fails
- Used in destructors
Common patterns used in this exercise:
-
RAII (Resource Acquisition Is Initialization):
- Resources managed through object lifetime
- Automatic cleanup in destructors
-
Delegation Pattern:
- Bureaucrat delegates form signing to Form class
- Clear separation of responsibilities
Different types of relationships:
-
Association:
- Bureaucrat uses Form
- Loose coupling between classes
-
Dependency:
- Form depends on Bureaucrat for signing
- One-way relationship
Implementing immutable attributes:
class Form {
private:
const std::string _name; // Immutable
const int _gradeToSign; // Immutable
bool _isSigned; // Mutable state
};
Benefits:
- Thread safety
- Predictable behavior
- Easier to reason about
Custom output formatting:
std::ostream& operator<<(std::ostream& os, const Form& form) {
os << "Form: " << form.getName()
<< "\nStatus: " << (form.getIsSigned() ? "Signed" : "Unsigned");
return os;
}
Features:
- Chainable operations
- Consistent formatting
- Readable output
-
Separation of Concerns:
- Each class has distinct responsibility
- Clear boundaries between functionality
-
Single Responsibility Principle:
- Form handles form-related operations
- Bureaucrat handles bureaucrat operations
-
Interface Segregation:
- Minimal public interfaces
- Only necessary operations exposed
- Exception Classes:
class GradeTooHighException : public std::exception {
const char* what() const throw() {
return "Grade too high";
}
};
- Error Checking:
void Form::beSigned(const Bureaucrat& b) {
if (b.getGrade() > _gradeToSign)
throw GradeTooLowException();
_isSigned = true;
}