0

我有一个抽象Node类,派生自许多子类,例如Color, Texture, Shape,Light等...包含我的应用程序用户数据。数据包含在这些节点的大树中。每个Node子类都有固定数量的子类,它们是固定类型的。例如,一个 Material 可以有一个 Color 子节点和一个 Texture 子节点。这些存储std::shared_ptr

目前,每个类都派生一个setChild方法,该方法以 aNode作为参数。每个实现都会相应地检查类型与其子类型,dynamic_cast如果成功则设置它。

我想在 中实现一个通用setChild方法Node,而不需要对其进行子类化。为此,每个子类将在构造函数中声明(或注册)其子类,方法是提供名称字符串、类型字符串(对应于子类)和指向 a 的shared_ptr指针Node

您现在可以看到问题:

  • 我将使用 a **SubClass,向上转换为 a **Node,我知道这很糟糕,但是由于每个子类都有一个type()唯一标识该类的方法,并且因为对于每个注册的子类我都知道它的类型,所以我可以仔细检查以避免存储错误的指针类型双指针。
  • 实际上,我不会使用 a 来执行此操作**Node,而是使用 a来执行此操作*std::shared_ptr<Node>。在这里,我不确定是否做对了。

问题:

  • 即使我确定类型,是否可以设置 a shared_ptr<Subclass>*shared_ptr<Node>
  • 这是按照您的设计方式设计的吗?

谢谢,

艾蒂安

4

2 回答 2

2

如果我理解你,以下是不安全的:

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++。

如果您正在寻找扩展 3rd 方插件中的节点集,唯一的解决方案是一系列动态转换,所以让我们看看我们可以做些什么来使注册工作。

与其尝试使用强制类型转换做所有事情,不如考虑使用模板来执行类型安全操作。有一个抽象基类,它接受节点的共享 ptr,或者使用它,或者不使用它

(从这里开始,我将使用 T::Ptr 而不是 boost::shared_ptr,假设 typedef 存在。这只是为了便于 stackoverflow 阅读)

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;
};
于 2013-09-07T00:28:14.043 回答
0

shared_pr 假设 static_pointer_cast 和 dynamic_pointer_cast 做你想做的事。但是,鉴于您有一组固定的节点类型,我强烈推荐访问者模式。鉴于您似乎在做一个场景图,我更倾向于推荐它,因为访问者的最佳用法是使用场景图。

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

最初,这种访问者模式很难接近。然而,它在场景图中非常受欢迎,因为它非常擅长处理一小组节点类型,而且它是解决问题的最安全的方法

于 2013-09-04T06:27:59.973 回答