galinette
galinette

Reputation: 9292

scene graph, shared pointers and constness propagation

I am working on an existing large project which basically:

Currently, children are stored using smart pointers, mostly to avoid deep copies when building the graph from file read and during user edition of the graph.

As the smart pointers (std::shared_ptr) do not propagate constness, I have the following options:

  1. Store the children using smart pointers to const objects. When recursively doing the initialization step, const_cast them to non-const pointers Store the children using smart pointers to const objects. For recursively doing the initialization step, const_cast them to non-const pointers. I do not like abusing of const_cast
  2. Store the children using smart pointers to const objects. When recursively doing the initialization step, do a deep copy of each children to a non-const object, initialize it, and replace the children by the initialized one. This is not efficient, each node is deep-copied during initialization
  3. Store the children using smart pointers to non-const objects. Then initialization is not a problem anymore, but all const member functions used during computation may call non const member functions of children, which is a potential source of bugs and clearly non const-correct.

I know that using smart pointers only to avoid deep copies during tree manipulation is not the nice way to implement this at the age of c++11 and move semantics. Rewriting all the code with move semantics might be done one day, but this represents quite a large work.

What would be, in your opinion, the nicest way to implement that pattern, without rewriting all with move semantics? I have thought of wrapping std::shared_ptr to propagate constness, are there other ideas?

Thanks!

Upvotes: 1

Views: 675

Answers (1)

Chris Drew
Chris Drew

Reputation: 15334

I would go with option 3 but store the children in private member variables in a base class or composed object.

Only allow access to the children via getters that enforce const correctness.

The const getter returning a raw pointer-to-const. The non-const getter returning a raw pointer or shared pointer.

Something like:

#include <iostream>
#include <memory>
#include <vector>

template<class Child>
class Parent {
 private: 
  std::vector<std::unique_ptr<Child>> children_;
 protected:
  ~Parent() = default;
 public:
  const Child* getChild(size_t child_number) const { 
    return children_.at(child_number).get(); 
  } 
  Child* getChild(size_t child_number) {
    return children_.at(child_number).get(); 
  } 
  size_t getNumberOfChildren() const {
    return children_.size();
  }
  void addChild(std::unique_ptr<Child> child) {
    children_.emplace_back(std::move(child));
  }
};

struct Node : Parent<Node> {
 private:
  std::string name_;
 public:
  Node(std::string name) : name_(std::move(name)) {}
  void print() const { std::cout << "Node: " << name_  << "\n";}
  void setName(const std::string& name) { name_ = name; }
  void wrong() const {
    //children_[0]->setName("Wrong"); // Not allowed
    //getChild(0)->setName("Wrong"); // Not allowed
  }
};

void printRecursive(const Node* node) {
  if (node) {
    node->print();
    for (size_t i=0; i!=node->getNumberOfChildren(); ++i)
      printRecursive(node->getChild(i)); 
  }
}

int main() {
  // Initialization
  Node root("Root");
  root.addChild(std::make_unique<Node>("Child 1"));
  root.addChild(std::make_unique<Node>("Child 2"));

  // "Computation" with pointer-to-const
  const Node* root_ptr = &root;
  printRecursive(root_ptr);
}

Live demo.

Live demo - using composition.

I've used unique_ptr instead of shared_ptr in my example because I can but you may have a good reason to use shared_ptr.

Upvotes: 0

Related Questions