A Double Dispatch Implementation

Note: well look at that, it’s been sitting here for years with a whole bunch of mistakes and no-one noticed. Tsk.

Well, it just seemed the Christmassy thing to do… implementing, tinkering and refining (?) double dispatch in C++. I’m not sure what idioms it uses… pimpl? visitor? Anyway, for this test, I’ve used “collisions” between Rock, Paper and Scissors. (As my recent excursion into Python showed, there are far more efficient methods to check Rock, Paper, Scissors. Here, they’re just used to illustrate a point.)

I’ve tried to minimise the amount of boiler plate. For each new class, apart from the new function(s) to handle collisions, the following needs to be added:

  • a forward declaration in CollisionHandler.h,
  • Invoke methods to CollisionHandlerBase and CollisionHandlerTemplate,
  • the new class has to implement an InvokeCollider method and call SetupCollisionHandler to create a new collision handler for itself.

Originally, that last step was done in the class constructor to avoid it being called multiple times elsewhere and overwriting/replacing an existing handler and causing a memory leak (although the original motivation was to stop unnecessary repeat assignments slowing execution). Using a smart pointer should avoid this problem.

There are three versions of CollisionHandler, one uses a raw (dumb) pointer, one an auto pointer and finally one uses a unique pointer.

I was quite pleased with myself when I sat down and wrote the first (working) version in one go. Further refinement, meant adding templates.

CollisionHandler.h
  1. #pragma once
  2. #include <iostream>
  3. #include <string>
  4.  
  5. struct GameObjBase;
  6. struct Rock;
  7. struct Paper; 
  8. struct Scissors;
  9.  
  10. template <class T1, class T2>
  11. void CollisionBetween(T1* p1, T2* p2) {
  12.   // generic/default handler or not handled
  13.   std::cout << "CollisionBetween Not handled: " << typeid(p1).name() << " ~ " << typeid(p2).name() << std::endl;
  14. };
  15.  
  16. template <class T1>
  17. void CollisionBetween(T1* p1, GameObjBase* p2) {
  18.   p2->InvokeCollider(p1->ch);
  19.   //p1->InvokeCollider(p2->ch);  // can be called symmetrically
  20.   std::cout << "\t" << p1->name << " - " << p2->name << std::endl;
  21. };
  22.  
  23. template <class T2>
  24. void CollisionBetween(GameObjBase* p1, T2* p2) {
  25.   p2->InvokeCollider(p1->ch);
  26.   //p1->InvokeCollider(p2->ch);  // can be called symmetrically
  27.   std::cout << "\t" << p1->name << " - " << p2->name << std::endl;
  28. };
  29.  
  30. void CollisionBetween(GameObjBase* p1, GameObjBase* p2);
  31.  
  32. void CollisionBetween(Rock* rock, Rock* rock1);
  33. void CollisionBetween(Rock* rock, Paper* paper);
  34. void CollisionBetween(Rock* rock, Scissors* scissors);
  35.  
  36. void CollisionBetween(Scissors* scissors, Rock* rock);
  37. void CollisionBetween(Scissors* scissors, Paper* paper);
  38. void CollisionBetween(Scissors* scissors, Scissors* scissors1);
  39.  
  40. void CollisionBetween(Paper* paper, Rock* rock);
  41. void CollisionBetween(Paper* paper, Paper* paper1);
  42. void CollisionBetween(Paper* paper, Scissors* scissors);
  43.  
  44. struct CollisionHandlerBase {
  45.   virtual void Invoke(Rock*)     { std::cout << "Invoke Not handled";};
  46.   virtual void Invoke(Paper*)    { std::cout << "Invoke Not handled";};
  47.   virtual void Invoke(Scissors*) { std::cout << "Invoke Not handled";};
  48. };
  49.  
  50. /*
  51. #define USE_DUMB_PTR
  52. #define USE_AUTO_PTR
  53. */
  54. #define USE_UNIQUE_PTR
  55. #ifdef USE_DUMB_PTR
  56. class CollisionHandler {
  57.   CollisionHandlerBase* pImplementation;
  58. public:
  59.   CollisionHandler() : pImplementation(NULL) {};
  60.   ~CollisionHandler() { delete(pImplementation); };
  61.   void SetupCollisionHandler(CollisionHandlerBase* pImplementation) { 
  62.     delete(pImplementation);
  63.     this->pImplementation = pImplementation;
  64.   };
  65.   template <class T>
  66.     void Invoke(T t) { pImplementation->Invoke(t); };
  67. };
  68. #endif
  69. #ifdef USE_AUTO_PTR
  70. class CollisionHandler {
  71.   std::auto_ptr pImplementation;
  72. public:
  73.   void SetupCollisionHandler(CollisionHandlerBase* pImplementation) { 
  74.     this->pImplementation.reset(pImplementation);
  75.   };
  76.   template <class T>
  77.     void Invoke(T t) { pImplementation->Invoke(t); };
  78. };
  79. #endif
  80. #ifdef USE_UNIQUE_PTR
  81. class CollisionHandler {
  82.   std::unique_ptr pImplementation;
  83. public:
  84.   void SetupCollisionHandler(CollisionHandlerBase* pImplementation) { 
  85.     this->pImplementation.reset(pImplementation);
  86.   };
  87.   template <class T>
  88.     void Invoke(T t) { pImplementation->Invoke(t); };
  89. };
  90. #endif
  91.  
  92. template <class T>
  93. class CollisionHandlerTemplate : public CollisionHandlerBase {
  94.   T * target;
  95. public:
  96.   CollisionHandlerTemplate(T * target_) :target(target_) { };
  97.  
  98.   void Invoke(Rock* rock)         { CollisionBetween(target, rock);     };
  99.   void Invoke(Paper* paper)       { CollisionBetween(target, paper);    };
  100.   void Invoke(Scissors* scissors) { CollisionBetween(target, scissors); };
  101. };
CollisionHandler.cpp
  1. #include "CollisionHandler.h"
  2. #include "gameobjects.h"
  3.  
  4. void CollisionBetween(GameObjBase* p1, GameObjBase* p2) {
  5.   p2->InvokeCollider(p1->ch);
  6.   //p1->InvokeCollider(p2->ch);   // can be called symmetrically
  7.   std::cout << "\t" << p1->name << " - " << p2->name << std::endl;
  8. };
  9.  
  10. void CollisionBetween(Rock* rock, Paper* paper) {
  11.   std::cout << "Rock wrapped by paper: " << std::endl;
  12. };
  13. void CollisionBetween(Rock* rock, Rock* rock1) {
  14.   std::cout << "Rock ignores rock : " << std::endl;
  15. };
  16. void CollisionBetween(Rock* rock, Scissors* scissors) {
  17.   std::cout << "Rock breaks scissors : " << std::endl;
  18. };
  19.  
  20. void CollisionBetween(Scissors* scissors, Rock* rock) { 
  21.   std::cout << "Scissors break on rock: " << std::endl;
  22. };
  23. void CollisionBetween(Scissors* scissors, Paper* paper) { 
  24.   std::cout << "Scissors cut paper: " << std::endl;
  25. };
  26. void CollisionBetween(Scissors* scissors, Scissors* scissors1 ){ 
  27.   std::cout << "Scissors ignore scissors: " << std::endl;
  28. };
  29.  
  30. void CollisionBetween(Paper* paper, Rock* rock) { 
  31.   std::cout << "Paper wraps rock: " << std::endl;
  32. };
  33. void CollisionBetween(Paper* paper, Paper* paper1) { 
  34.   std::cout << "Paper ignores paper: " << std::endl;
  35. };
  36. void CollisionBetween(Paper* paper, Scissors* scissors) { 
  37.   std::cout << "Paper cut by scissors: " << std::endl;
  38. };
GameObjects.h
  1. #pragma once
  2. #include "CollisionHandler.h"
  3.  
  4. struct GameObjBase {
  5.   std::string name;
  6.   CollisionHandler ch;
  7.   virtual void InvokeCollider(CollisionHandler& ch) = 0;
  8.  
  9.   template <class T>
  10.     void SetupCollisionHandler(CollisionHandler& ch, T* t) {
  11.     ch.SetupCollisionHandler(new CollisionHandlerTemplate<T>(t));
  12.   }
  13. };
  14.  
  15. struct Rock : GameObjBase {
  16.   Rock();
  17.   void InvokeCollider(CollisionHandler& ch);
  18. };
  19.  
  20. struct Paper : GameObjBase {
  21.   Paper();
  22.   void InvokeCollider(CollisionHandler& ch);
  23. };
  24.  
  25. struct Scissors : GameObjBase {
  26.   Scissors();
  27.   void InvokeCollider(CollisionHandler& ch);
  28. };
GameObjects.cpp
  1. #include "gameobjects.h"
  2.  
  3. Rock::Rock() { SetupCollisionHandler(ch, this); };
  4. void Rock::InvokeCollider(CollisionHandler& ch) { ch.Invoke(this); }
  5.  
  6. Paper::Paper() { SetupCollisionHandler(ch, this); };
  7. void Paper::InvokeCollider(CollisionHandler& ch) { ch.Invoke(this); }
  8.  
  9. Scissors::Scissors() { SetupCollisionHandler(ch, this); }; 
  10. void Scissors::InvokeCollider(CollisionHandler& ch) { ch.Invoke(this); }
main.cpp
  1. #include <vector>
  2. #include <iostream>
  3. #include "gameobjects.h"
  4. #include "CollisionHandler.h"
  5.  
  6. void main() {
  7.   Rock rock, rock1;
  8.   rock.name = "Pierre";
  9.   rock1.name = "Rocky";
  10.   Paper paper;
  11.   paper.name = "Whitey";
  12.   Scissors scissors;
  13.   scissors.name = "Snippy";
  14.  
  15.   GameObjBase* pRock = &rock;
  16.   GameObjBase* pRock1 = &rock1;
  17.   GameObjBase* pPaper = &paper;
  18.   GameObjBase* pScissors = &scissors;
  19.  
  20.   std::cout << "rock* & scissors" << std::endl;
  21.   CollisionBetween(pRock, &scissors);
  22.   std::cout << std::endl;
  23.   std::cout << "scissors & rock*" << std::endl;
  24.   CollisionBetween(&scissors, pRock1);
  25.   std::cout << std::endl;
  26.   CollisionBetween(pRock, pRock1);
  27.  
  28.   std::vector<GameObjBase*> vecGameObjBase;
  29.   vecGameObjBase.push_back(pRock);
  30.   vecGameObjBase.push_back(pPaper);
  31.   vecGameObjBase.push_back(pScissors);
  32.  
  33.   for (size_t i = 0; i < vecGameObjBase.size(); i++) {
  34.     for (size_t j = 0; j < vecGameObjBase.size(); j++) {
  35.       CollisionBetween(vecGameObjBase[i], vecGameObjBase[j]);
  36.     }
  37.     std::cout << std::endl;
  38.   }
  39.  
  40.   std::cin.get();
  41. }