У меня есть аннотация Node
класс, производный от многих подклассов, таких как Color
, Texture
, Shape
, Light
и т. д. … содержащие пользовательские данные моего приложения. Данные состоят из большого дерева этих узлов.
каждый Node
подкласс имеет фиксированное количество дочерних элементов, которые имеют фиксированные типы. Например, материал может иметь одного дочернего элемента Color и одного дочернего элемента Texture. Они хранятся как std::shared_ptr
«s
В настоящее время каждый класс получает setChild
метод, который принимает Node
в качестве аргумента. Каждая реализация проверяет тип по сравнению со своим дочерним типом с помощью dynamic_cast
соответственно, и устанавливает его в случае успеха.
Я хотел бы реализовать общий setChild
метод в Node
без необходимости создавать подклассы. Для этого каждый подкласс должен объявить (или зарегистрировать) своих потомков в конструкторе, указав строку имени, строку типа (соответствующую подклассу) и указатель на shared_ptr
к Node
,
Вы можете увидеть проблему сейчас:
**SubClass
, восходящий к **Node
, что я знаю, это плохо, но так как каждый подкласс имеет type()
метод уникальной идентификации класса, и поскольку для каждого зарегистрированного дочернего элемента я знаю его тип, я могу выполнить двойную проверку, чтобы избежать хранения неправильных типов указателей с двойным указателем.**Node
, но с *std::shared_ptr<Node>
, Здесь я не уверен, что поступаю правильно.Вопросы:
shared_ptr<Subclass>
с *shared_ptr<Node>
даже если я уверен в типе?Спасибо,
Etienne
Если я вас понимаю, следующее небезопасно:
boost::shared_ptr<SpecificNode> realPtr;
boost::shared_ptr<Node>* castPtr;
// castPtr = &realPtr; -- invalid, cannot cast from one to the other
castPtr = reinterpret_cast<boost::shared_ptr<Node>*>(&castPtr);
castPtr->reset(anything); // illegal. castPtr does not point
// to a boost::shared_ptr<Node*>
Теперь вам может повезти, и память может выстроиться в линию, но это недопустимо в C ++.
Если вы хотите расширить набор узлов в сторонних плагинах, единственное решение — это серия динамических приведений, поэтому давайте посмотрим, что мы можем сделать, чтобы регистрация работала с этим.
Вместо того, чтобы пытаться делать все с приведением типов, рассмотрите возможность использования шаблонов для выполнения безопасных действий. Иметь базовый класс abtract, который принимает общий ptr для узла и либо использует его, либо не использует его
(начиная с этого момента, я собираюсь использовать T :: Ptr вместо boost :: shared_ptr, предполагая, что здесь есть typedef. Это просто для облегчения чтения через стековый поток)
class Registration
{
public:
typedef boost::shared_ptr<Registration> Ptr;
virtual bool consume(const Node::Ptr&) = 0;
};
template <typename T>
class SpecificRegistration : public Registration
{
public:
SpecificRegistration(T::Ptr& inChildPtr)
: mChildPtr(inChildPtr)
{ }
virtual bool consume(const Node:Ptr& inNewValue)
{
if(!inNewValue) {
mChildPtr.reset();
return true; // consumed null ptr
} else {
T::Ptr newValue = dynamic_pointer_cast<T>(inNewValue);
if (newValue) {
mChildPtr = newValue;
return true; // consumed new value
} else {
return false; // no match
}
}
}
private:
T::Ptr& mChildPtr;
};
template <typename T>
Registration::Ptr registerChild(T::Ptr& inChildPtr)
{
return make_shared<SpecificRegistration<T> >(inChildPtr);
}
// you can also register vector<T::Ptr> if you write an
// ArraySpecificRegistration class which uses push_back when
// it consumes a node
void Node::setNode(const Node& inNode) {
for (RegistrationList::iterator iter = mRegistration.begin(); iter != mRegistration.end(); ++iter) {
if (mRegistration->consume(inNode))
return;
}
throw runtime_error("Failed to set any children of the node");
}
class SomeThirdPartyNode
: public Node
{
public:
SomeThirdPartyNode()
{
// all they have to write is one line, and mOtherTHing is
// registered
addChild(registerChild(mOtherThing));
}
private:
SomeOtherThirdPartyNode::Ptr mOtherThing;
};
shared_pr предполагает static_pointer_cast и dynamic_pointer_cast, которые делают то, что вы хотите. Однако, учитывая, что у вас есть фиксированный набор типов узлов, я настоятельно рекомендую шаблон посетителей. Учитывая, что вы, кажется, делаете график сцены, я даже более склонен рекомендовать его, поскольку лучшее использование Visitor — с графиками сцены.
class Material;
class Node;
class Color;
class Texture;
class Shape;
class Light;
class Visitor
{
public:
virtual void visit(const shared_ptr<Material>& inNode) = 0;
virtual void visit(const shared_ptr<Color>& inNode) = 0;
virtual void visit(const shared_ptr<Texture>& inNode) = 0;
virtual void visit(const shared_ptr<Shape>& inNode) = 0;
virtual void visit(const shared_ptr<Light>& inLight) = 0;
}
class Node
{
public:
virtual void accept(Visitor& inVisitor) = 0;
};
class Color
: public Node
, public boost::enable_shared_from_this<Color>
{
public:
virtual void accept(Visitor& inVisitor)
{
inVisitor.visit(shared_from_this());
}
...
};
class Texture
: public Node
, public boost::enable_shared_from_this<Texture>
{
public:
virtual void accept(Visitor& inVisitor)
{
inVisitor.visit(shared_from_this());
}
...
};
class Shape
: public Node
, public boost::enable_shared_from_this<Shape>
{
public:
virtual void accept(Visitor& inVisitor)
{
inVisitor.visit(shared_from_this());
}
...
};
class Light
: public Node
, public boost::enable_shared_from_this<Light>
{
public:
virtual void accept(Visitor& inVisitor)
{
inVisitor.visit(shared_from_this());
}
...
};
Цель всего этого шаблона — сделать что-то похожее на dynamic_cast, только это делается с помощью пары виртуальных вызовов функций вместо произвольного dynamic_cast. Node :: accept отвечает за вызов функции, которая знает точный тип объекта (т.е. Light :: accept знает, что this — это Light *). Затем он вызывает функцию посещения посетителя с «правильной» информацией о типе.
Эта система разработана для поддержки множества алгоритмов, которые работают с небольшим набором типов. Например, ваша функция setChild
class SetMaterialChild
: public Visitor
{
public:
SetMaterialChild(Material& inMaterial)
: mMaterial(inMaterial)
{ }
virtual void visit(const shared_ptr<Color>& inNode)
{
mMaterial.mColor = inNode;
}
virtual void visit(const shared_ptr<Texture>& inNode)
{
mMaterial.mTexture = inNode;
}
virtual void visit(const shared_ptr<Shape>& inNode)
{
throw std::runtime_error("Materials cannot have shapes");
}
virtual void visit(const shared_ptr<Light>& inLight)
{
throw std::runtime_error("Materials cannot have lights");
}private:
Material& mMaterial)
};class Material
: public Node
, public boost::enable_shared_from_this<Material>
{
public:
virtual void accept(Visitor& inVisitor)
{
inVisitor.visit(shared_from_this());
}
void setNode(const shared_ptr<Node>& inNode)
{
SetMaterialChild v(*this);
inNode->accept(v);
}
...
};
К этому шаблону посетителей трудно подойти изначально. тем не менее, он ДЕЙСТВИТЕЛЬНО популярен для графов сцен, потому что он необычайно хорош в обработке небольшого набора типов узлов и является наиболее безопасным способом решения проблемы.