【发布时间】:2017-04-05 00:27:46
【问题描述】:
我确定这是个坏主意。让我们假装我有充分的理由这样做。我有一棵成功使用静态多态性传递消息的节点树。至关重要的是,每个节点不能知道它连接的节点的类型,它只知道它传递的消息的类型。为了遍历树,我使用 CRTP 实现了访问者模式。这适用于树的第一级。
但是,当遍历到树的第二层时,下一个节点的类型会使用下面的 AnyNode 类擦除。我一直无法弄清楚如何从擦除类型向下转换为具体类型。下面的示例在测试中有效,但我认为它也可能非常危险,并且只是靠碰巧布置内存的位置来工作。
我必须删除AnyNode::Model<T>::acceptDispatch 中的访问者类型似乎有问题,这在AnyNode::Concept::accept 中是完全已知的。但我不知道如何从概念向下转换到概念中的模型in(我尝试了协变虚拟cast 函数,但没有奏效)。而且我不能使用虚拟方法将类型化的访问者传递给派生的模型类,因为虚拟方法不能被模板化。
有没有一种安全的方法来调用node.accept 并传递访问者,而不必删除访问者的类型然后将其静态转换回来?有什么方法可以在运行时将概念向下转换为Model<T>?有没有更好的方法来解决这个问题?是不是有一些疯狂的 C++11 新方法可以解决这个问题,可能使用 SFINAE?
class AnyNode
{
struct Concept
{
virtual ~Concept() = default;
template< typename V >
void accept( V & visitor )
{
acceptDispatch( &visitor );
}
virtual void acceptDispatch( VisitorBase * ) = 0;
};
template< typename T >
struct Model : public Concept
{
Model( T &n ) : node( n ) {}
void acceptDispatch( VisitorBase * v ) override
{
// dynamic cast doesn't work, probably for good reason
NodeVisitor< T >* visitor = static_cast< NodeVisitor< T >* >( v );
std::cout << "CAST" << std::endl;
if ( visitor ) {
std::cout << "WAHOO" << std::endl;
node.accept( *visitor );
}
}
private:
T &node;
};
std::unique_ptr< Concept > mConcept;
public:
template< typename T >
AnyNode( T &node ) :
mConcept( new Model< T >( node )) {}
template< typename V >
void accept( V & visitor )
{
mConcept->accept( visitor );
}
};
编辑这里是访问者基类,以及派生访问者的示例。派生的访问者由客户端代码实现(这是库的一部分),因此基类无法知道将实现什么访问者。恐怕这会分散中心问题的注意力,但希望它有助于解释这个问题。此处的所有内容都有效,除非在 outlet_visitor::operator() 中的 AnyNode 指针上调用 ->accept( visitor )。
// Base class for anything that implements accept
class Visitable
{
public:
};
// Base class for anything that implements visit
class VisitorBase
{
public:
virtual ~VisitorBase() = default;
};
// Visitor template class
template< typename... T >
class Visitor;
template< typename T >
class Visitor< T > : public VisitorBase
{
public:
virtual void visit( T & ) = 0;
};
template< typename T, typename... Ts >
class Visitor< T, Ts... > : public Visitor< Ts... >
{
public:
using Visitor< Ts... >::visit;
virtual void visit( T & ) = 0;
};
template< class ... T >
class NodeVisitor : public Visitor< T... >
{
public:
};
// Implementation of Visitable for nodes
template< class V >
class VisitableNode : public Visitable
{
template< typename T >
struct outlet_visitor
{
T &visitor;
outlet_visitor( T &v ) : visitor( v ) {}
template< typename To >
void operator()( Outlet< To > &outlet )
{
for ( auto &inlet : outlet.connections()) {
auto n = inlet.get().node();
if ( n != nullptr ) {
// this is where the AnyNode is called, and where the
// main problem is
n->accept( visitor );
}
}
}
};
public:
VisitableNode()
{
auto &_this = static_cast< V & >( *this );
_this.each_in( [&]( auto &i ) {
// This is where the AnyNode is stored on the inlet,
// so it can be retrieved by the `outlet_visitor`
i.setNode( *this );
} );
}
template< typename T >
void accept( T &visitor )
{
auto &_this = static_cast< V & >( *this );
std::cout << "VISITING " << _this.getLabel() << std::endl;
visitor.visit( _this );
// The outlets are a tuple, so we use a templated visitor which
// each_out calls on each member of the tuple using compile-time
// recursion.
outlet_visitor< T > ov( visitor );
_this.each_out( ov );
}
};
// Example instantiation of `NodeVistor< T... >`
class V : public NodeVisitor< Int_IONode, IntString_IONode > {
public:
void visit( Int_IONode &n ) {
cout << "Int_IONode " << n.getLabel() << endl;
visited.push_back( n.getLabel());
}
void visit( IntString_IONode &n ) {
cout << "IntString_IONode " << n.getLabel() << endl;
visited.push_back( n.getLabel());
}
std::vector< std::string > visited;
};
【问题讨论】:
-
为什么
dynamic_cast不起作用? -
模型或访问者的数量是否有限?它们可以在任何地方枚举吗?什么是
VisitorBase?这是 3 个问题,请全部回答 3 个。 -
为了回答你的两个问题,我添加了周围的代码。希望不是TMI。 @1201ProgramAlarm 我认为 dynamic_cast 不起作用,因为
NodeVisitor< T >只是访问者类层次结构的一部分。 -
@Yakk 访问者或模型的数量不是有限的,因为访问者和节点是由库的客户端实现的。但是,正如您在编辑中看到的那样,模型应该都有一个对应的 VisitableNode CRTP 类。我的一个问题是,如果某种模板魔法可以让我枚举访问者类型,而无需对它们进行硬编码。
VisitorBase只是访客的占位符类,但始终通过Visitor< T... >派生。
标签: c++ c++11 templates visitor-pattern double-dispatch