Combining RTTI with design patterns like the Visitor pattern can create powerful and flexible architectures. The Visitor pattern allows you to add new operations to an object structure without modifying the objects themselves. When combined with RTTI, it can provide even more flexibility. Let's explore how to do this:
First, let's implement a basic Visitor pattern. We implement a basic Shape
class, that can Accept()
visitors. We then create an AreaCalculator
visitor that uses this mechanism to calculate the total area of a collection of different Shape
subtypes:
#include <iostream>
#include <vector>
#include <memory>
// Forward declarations
class Circle;
class Rectangle;
class ShapeVisitor {
public:
virtual void Visit(Circle& circle) = 0;
virtual void Visit(Rectangle& rectangle) = 0;
virtual ~ShapeVisitor() = default;
};
class Shape {
public:
virtual void Accept(ShapeVisitor& visitor) = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
Circle(double radius) : radius_(radius) {}
void Accept(ShapeVisitor& visitor) override {
visitor.Visit(*this);
}
double GetRadius() const { return radius_; }
private:
double radius_;
};
class Rectangle : public Shape {
public:
Rectangle(double width, double height)
: width_(width), height_(height) {}
void Accept(ShapeVisitor& visitor) override {
visitor.Visit(*this);
}
double GetWidth() const { return width_; }
double GetHeight() const { return height_; }
private:
double width_, height_;
};
class AreaCalculator : public ShapeVisitor {
public:
void Visit(Circle& circle) override {
total_area_ += 3.14159 * circle.GetRadius()
* circle.GetRadius();
}
void Visit(Rectangle& rectangle) override {
total_area_ += rectangle.GetWidth()
* rectangle.GetHeight();
}
double GetTotalArea() const { return total_area_; }
private:
double total_area_ = 0.0;
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(
5.0));
shapes.push_back(std::make_unique<Rectangle>(
4.0, 6.0));
AreaCalculator areaCalc;
for (const auto& shape : shapes) {
shape->Accept(areaCalc);
}
std::cout << "Total area: "
<< areaCalc.GetTotalArea();
}
Total area: 102.54
Now, let's create another visitor, this time using RTTI. Our FlexibleVisitor
type has a HandleShape()
method, which uses RTTI via dynamic_cast
to implement different behaviours based on the specific subtype of Shape
it is visiting:
#include <iostream>
#include <vector>
#include <memory>
#include <typeinfo>
// Forward declarations
class Circle;
class Rectangle;
class ShapeVisitor {/*...*/}
class Shape {/*...*/}
class Circle : public Shape {/*...*/}
class Rectangle : public Shape {/*...*/}
class AreaCalculator : public ShapeVisitor {/*...*/}
class FlexibleVisitor : public ShapeVisitor {
public:
void Visit(Circle& circle) override {
std::cout << "Visiting a Circle\n";
HandleShape(circle);
}
void Visit(Rectangle& rectangle) override {
std::cout << "Visiting a Rectangle\n";
HandleShape(rectangle);
}
private:
template <typename T>
void HandleShape(T& shape) {
if (auto* circle =
dynamic_cast<Circle*>(&shape)
) {
std::cout << "Circle radius: "
<< circle->GetRadius() << "\n";
} else if (auto* rectangle =
dynamic_cast<Rectangle*>(&shape)
) {
std::cout << "Rectangle dimensions: "
<< rectangle->GetWidth() << "x"
<< rectangle->GetHeight() << "\n";
}
// We can add more type checks here without
// modifying Shape classes
}
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(
5.0));
shapes.push_back(std::make_unique<Rectangle>(
4.0, 6.0));
AreaCalculator areaCalc;
FlexibleVisitor flexVisitor;
for (const auto& shape : shapes) {
shape->Accept(areaCalc);
shape->Accept(flexVisitor);
}
std::cout << "Total area: "
<< areaCalc.GetTotalArea();
}
Visiting a Circle
Circle radius: 5
Visiting a Rectangle
Rectangle dimensions: 4x6
Total area: 102.54
This combination allows us to:
AreaCalculator
).FlexibleVisitor
).Shape
classes.FlexibleVisitor
without modifying existing Shape
classes.Remember, while this approach offers flexibility, it should be used judiciously. Overuse of RTTI can lead to code that's harder to maintain and potentially less efficient.
Always consider whether polymorphism through virtual functions might be a simpler and more appropriate solution for your specific use case.
Answers to questions are automatically generated and may not have been reviewed.
typeid()
Learn to identify and react to object types at runtime using RTTI, dynamic casting and the typeid()
operator