Run Time Type Information (RTTI) and typeid()

Combining RTTI with Visitor Pattern

How can I combine RTTI with design patterns like Visitor to create more flexible architectures?

Illustration representing computer hardware

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:

Basic Visitor Pattern

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

Implementing Visitors with RTTI

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:

  1. Use the Visitor pattern for type-safe operations (like AreaCalculator).
  2. Use RTTI for more flexible operations (like FlexibleVisitor).
  3. Add new operations without modifying the Shape classes.
  4. Handle new types in 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.

This Question is from the Lesson:

Run Time Type Information (RTTI) and typeid()

Learn to identify and react to object types at runtime using RTTI, dynamic casting and the typeid() operator

Answers to questions are automatically generated and may not have been reviewed.

This Question is from the Lesson:

Run Time Type Information (RTTI) and typeid()

Learn to identify and react to object types at runtime using RTTI, dynamic casting and the typeid() operator

A computer programmer
Part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

This course includes:

  • 125 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Free, Unlimited Access

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved