【问题标题】:scene graph, shared pointers and constness propagation场景图、共享指针和常量传播
【发布时间】:2014-11-28 12:55:43
【问题描述】:

我正在从事一个现有的大型项目,基本上:

  • 使用场景图,其中每个节点都有子槽
  • 执行初始化步骤,其中每个节点递归地初始化其子节点,并对自己的数据成员执行一些预计算/清理任务。
  • 然后启动大型计算,然后以纯只读模式从 const 成员函数访问场景图

目前,使用智能指针存储子级,主要是为了在从文件读取构建图形时以及在用户编辑图形期间避免深度复制。

由于智能指针(std::shared_ptr)不传播常量,我有以下选择:

  1. 使用指向 const 对象的智能指针存储子级。什么时候 递归地执行初始化步骤,const_cast 将它们 非常量指针 使用指向 const 的智能指针存储子项 对象。对于递归执行初始化步骤, const_cast 它们指向非常量指针。我不喜欢滥用 const_cast
  2. 使用指向 const 对象的智能指针存储子级。什么时候 递归地执行初始化步骤,将每个子对象深拷贝到一个非常量对象,初始化它,然后用初始化的对象替换子对象。这样效率不高,初始化时每个节点都被深度复制
  3. 使用指向非常量对象的智能指针存储子级。那么初始化就不再是问题了,但是在计算过程中使用的所有 const 成员函数都可能调用孩子的非 const 成员函数,这是一个潜在的错误来源,并且显然是非 const 正确的。

我知道在树操作期间仅使用智能指针来避免深度复制并不是在 c++11 时代实现这一点并移动语义的好方法。用移动语义重写所有代码可能有一天会完成,但这代表着相当大的工作。

在您看来,在不使用移动语义重写所有内容的情况下,实现该模式的最佳方式是什么?我曾想过包装 std::shared_ptr 以传播 constness,还有其他想法吗?

谢谢!

【问题讨论】:

  • 使用原始指针怎么样?可能,在您的数据结构中,唯一的更改应该是节点析构函数(删除所有子节点)和节点删除方法(删除被删除的节点)。但是,如果事情比我想象的要复杂,那就做包装吧。
  • @AndreSassi 原始指针不会比智能指针更好地传播常量。使用原始指针也会让生活变得困难,尤其是节点复制行为。
  • “由于智能指针 (std::shared_ptr) 不会传播 constness” 如果这些智能指针不是公共数据成员,您可以手动执行此操作:不要修改在const 成员函数中指向的对象(并且不要在const 成员函数中提供对该对象的非常量访问)。
  • @dyp :是的,但是错误总是可能的,例如调用子的非常量成员函数。编译器不会抱怨。如果您必须手动考虑不在 const 函数中执行非常量操作,那么 const 的全部目的就失去了
  • 您可以将智能指针包装在提供“深度常量”的类中..

标签: c++ c++11 constants smart-pointers


【解决方案1】:

我会选择选项 3,但将子级存储在基类或组合对象的私有成员变量中。

只允许通过强制 const 正确性的 getter 访问子级。

const getter 返回一个指向 const 的原始指针。返回原始指针或共享指针的非常量 getter。

类似:

#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.

我在示例中使用了unique_ptr 而不是shared_ptr,因为我可以,但您可能有充分的理由使用shared_ptr

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-23
    • 1970-01-01
    相关资源
    最近更新 更多