C++ avoid casting in derived class code for twin container/contained object design

98 views Asked by At

I have twin base classes for a container class and contained object (in my case, for a generic graph class made of linked nodes, but I guess the question would stand in all cases where you want to derive from both a container class and a contained object class from twin container/contained base classes). I want to use derived classes of both the container and contained object. How do I avoid having to constantly downcast the contained object to the derived contained object type when I access the container of the derived container class?

// a class that represents the graph
template <typename WrappedObject> class BaseGraph  {
  ... // constructor, virtual destructor, ...
public:
  std::map<WrappedObject*, BaseNode<WrappedObject> *> obj_to_node_map_;
  bool addEdge(WrappedObject *scr, WrappedObject *trg) {
    BaseNode<WrappedObject > * srcNode = getOrCreateNode(src);
    BaseNode<WrappedObject > * dstNode = getOrCreateNode(trg);
    return addEdge(srcNode, dstNode);
  }

  bool addEdge(BaseNode<WrappedObject> * src, BaseNode<WrappedObject> * trg) {
    if (src == trg) {
      return false;
    }

    src->suc_.insert(trg);
    trg->pre_.insert(src);
    return true;
  }

  BaseNode<WrappedObject>* getOrCreateNode(WrappedObject*obj){
    BaseNode<WrappedObject> *node = getNode(obj);
    if (node == nullptr) {
      node = createNode(obj);
      obj_to_node_map_[obj] = node;
    }
    return node;
  }

  BaseNode<WrappedObject>* createNode(WrappedObject* obj){
    BaseNode<WrappedObject> *node = allocateNode(obj);
    nodes_.push_back(node);
    return node;
  }
  BaseNode<WrappedObject>* getNode(WrappedObject*obj) const {
    auto it = obj_to_node_map_.find(obj);
    if (it == obj_to_node_map_.end()) {
      return nullptr;
    }
    return it->second;
  }
private:
  virtual BaseNode<Object> * allocateNode(Object *obj) = 0;

};
// a class that represents the vertices of the graph
template <typename WrappedObject>
class BaseNode {
  friend class BaseGraph<WrappedObject>;
public:
  typedef std::set<BaseNode *> Set;
  BaseNode(WrappedObject*obj):obj_(obj) {
  }
  virtual ~BaseNode() {};

  WrappedObject *obj_;
  Set suc_;
  Set pre_;
};

// derived classes for node and graph
class RealObject;
class MyNode : public BaseNode<RealObject> {
private:
  std::string name_;
  MyNode(RealObject *obj, std::string & name): BaseGraph<RealObject>(obj), name_(name) {}
...
};
class MyGraph: public BaseGraph<RealObject> {
  // constructor, virtual destructor, ...
private:
  virtual BaseNode<RealObject> * allocateNode(RealObject *obj) {
    return new MyNode(obj, this);
  }
  std::map<std::string, MyNode *> name_lu_map_;
public:
  MyNode * getNode(std:string & name) {
    return(name_lu_map_[name]);
  }
  // Have to have this otherwise any call to getNode on a MyGraph instance
  // will cause compiler error as getNode is overloaded in derived class
  MyNode * getNode(RealObject *obj) const {
    return static_cast<MyNode *>(BaseGraph<RealObject>::getNode(obj));
  }

  void myDerivedClassFunction(RealObject *obj) {
    // Lines like the following when I call a method of the base class
    // returning a base class pointer will require casting, which I find
    // ugly
    MyNode *node = static_cast<MyNode *>(getOrCreateNode(obj));
    node->name_ = <something>;
    ...
  }
};

Is there a design pattern that would allow to avoid all the awkward downcast to the derived class whenever I use methods of the container base class that return base class contained object pointers? I thought of using a template argument to define what the derived contained object type will be, but I do not see how I can describe in C++ the fact that a typename template argument derives from another class. Possibly, I am looking at this from the wrong angle. Any ideas?

0

There are 0 answers