граф сцены, общие указатели и распространение констант

Я работаю над существующим большим проектом, который в основном:

  • Использует граф сцены, где каждый узел имеет дочерние слоты
  • Выполняет этап инициализации, на котором каждый узел рекурсивно инициализирует своих дочерних узлов и выполняет некоторые задачи предварительного расчета / очистки для своих собственных элементов данных.
  • Затем запускается большое вычисление, доступ к графу сцены осуществляется в чистом режиме только для чтения только из константных функций-членов.

В настоящее время дочерние элементы хранятся с использованием интеллектуальных указателей, в основном, чтобы избежать глубоких копий при построении графа из файла, прочитанного и во время пользовательского редактирования графа.

Поскольку интеллектуальные указатели (std :: shared_ptr) не распространяют константность, у меня есть следующие варианты:

  1. Храните дочерние элементы, используя интеллектуальные указатели на константные объекты. При рекурсивном выполнении шага инициализации const_cast их в неконстантные указатели. Сохраните дочерние элементы, используя интеллектуальные указатели на константные объекты. Для рекурсивного выполнения шага инициализации const_cast их неконстантным указателям. Я не люблю злоупотреблять const_cast
  2. Храните дочерние элементы, используя интеллектуальные указатели на константные объекты. При рекурсивном выполнении шага инициализации сделайте глубокую копию каждого дочернего объекта в неконстантный объект, инициализируйте его и замените потомков инициализированным. Это неэффективно, каждый узел глубоко копируется во время инициализации.
  3. Храните дочерние элементы, используя интеллектуальные указатели на неконстантные объекты. Тогда инициализация больше не проблема, но все константные функции-члены, используемые во время вычислений, могут вызывать неконстантные функции-члены дочерних элементов, что является потенциальным источником ошибок и явно некорректно с константой.

Я знаю, что использование интеллектуальных указателей только для предотвращения глубоких копий во время манипуляции с деревом - не лучший способ реализовать это в эпоху С ++ 11 и переместить семантику. Когда-нибудь можно будет переписать весь код с семантикой перемещения, но это довольно большая работа.

Каким был бы, по вашему мнению, лучший способ реализовать этот шаблон, не переписывая все с помощью семантики перемещения? Я подумал об упаковке std :: shared_ptr для распространения константности, есть ли другие идеи?

Спасибо!


person galinette    schedule 03.10.2014    source источник
comment
Как насчет использования необработанных указателей? Вероятно, в вашей структуре данных единственные изменения должны быть в деструкторе узла (для удаления всех дочерних узлов) и методе удаления узла (для удаления удаляемого узла). Однако если все будет сложнее, чем я думаю, тогда сделайте обертку.   -  person André Sassi    schedule 05.10.2014
comment
@AndreSassi Необработанные указатели не будут распространять константность лучше, чем интеллектуальные указатели. Кроме того, использование необработанных указателей усложнит жизнь, особенно при копировании узлов.   -  person Chris Drew    schedule 05.10.2014
comment
Поскольку интеллектуальные указатели (std :: shared_ptr) не распространяют константность, вы можете сделать это вручную, если эти интеллектуальные указатели не являются членами общедоступных данных: не изменяйте объект, на который указывается в const функциях-членах ( и не предоставлять неконстантный доступ к этому объекту в const функциях-членах).   -  person dyp    schedule 05.10.2014
comment
@dyp: да, но всегда возможны ошибки, такие как вызов неконстантной функции-члена дочернего элемента. И компилятор жаловаться не будет. Вся цель константности теряется, если вам нужно вручную думать о том, чтобы не выполнять неконстантные операции в константных функциях.   -  person galinette    schedule 05.10.2014
comment
Вы можете обернуть интеллектуальный указатель в класс, обеспечивающий глубокую константу.   -  person dyp    schedule 05.10.2014


Ответы (1)


Я бы выбрал вариант 3, но сохранил потомков в частных переменных-членах базового класса или составного объекта.

Разрешить доступ к дочерним элементам только через геттеры, которые обеспечивают правильность констант.

Геттер const возвращает необработанный указатель на const. Неконстантный геттер, возвращающий необработанный указатель или общий указатель.

Что-то вроде:

#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);
}

Живая демонстрация.

Живая демонстрация - с использованием композиции.

В моем примере я использовал unique_ptr вместо shared_ptr, потому что могу, но у вас может быть веская причина использовать shared_ptr.

person Chris Drew    schedule 05.10.2014