多态树类中的双指针upcast(再次)、shared_ptr和泛型setChild函数

Double pointer upcast (again), shared_ptr and generic setChild function in a polymorphic tree class

本文关键字:shared ptr 函数 setChild 泛型 再次 upcast 指针 多态      更新时间:2023-10-16

我有一个抽象的Node类,派生自许多子类,如ColorTextureShapeLight等……其中包含我的应用程序用户数据。数据包含在这些节点的大型树中。每个Node子类都有固定数量的子类,这些子类是固定类型的。例如,"材质"可以有一个子"颜色"和一个子"纹理"。这些存储为std::shared_ptr

目前,每个类都派生一个setChild方法,该方法以Node作为参数。每个实现使用dynamic_cast相应地检查类型及其子类型,如果成功,则进行设置。

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

你现在可以看到问题了:

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

问题:

  • 即使我确定类型,也可以用*shared_ptr<Node>设置shared_ptr<Subclass>
  • 这是你设计的方式吗

谢谢,

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

如果你想在第三方插件中扩展节点集,唯一的解决方案是一系列动态强制转换,所以让我们看看我们可以做些什么来实现注册。

与其尝试使用强制转换执行所有操作,不如考虑使用模板执行类型安全操作。有一个abtral基类,它接受到节点的共享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;
};

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

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