RecursiveASTVisitor 如何称呼 VisitNamedDecl 和 VisitCXXRecordDecl



RecursiveASTVisitor的文档中,没有名称为VisitNamedDecl,VisitCXXRecordDecl的虚拟方法。等。 那么如何称呼它们自动调用呢?

有两件事使设计RecursiveASTVisitor难以理解:

  1. 它使用 奇怪的重复出现的模板模式 (CRTP)调度到用户提供的方法,而不是更多的 使用C++virtual的传统(但可能更慢)技术 方法。

  2. 绝大多数可重写的方法都是使用 有点复杂的宏系统和多包含的头文件, 后者由构建过程中的特殊工具生成。 后果之一是 氧气,产生 您(和我)链接到的已发布文档看不到它们。

调度设计在 详细说明 部分,但我将提供我自己的解释 填写一些缺失的详细信息。

由于第二个问题,我将使用预处理器的片段 输出。 为了具体性,我正在预处理deleted-ctor.cc,我最近发布的代码 这个答案,但是 任何预处理形式的RecursiveASTVisitor.h都应与 我正在展示的部分。

访客调度机制

让我们专注于VisitNamedDecl因为它在 问题。 模板类定义,经过预处理(以及一些 使用indent缩进 工具和其他小的编辑以提高可读性),看起来像:

template <typename Derived>
class RecursiveASTVisitor {
public:
...
Derived &getDerived() { return *static_cast<Derived *>(this); }
...
bool TraverseDecl(Decl *D);
bool TraverseLabelDecl(LabelDecl *D);
...
bool WalkUpFromDecl(Decl *D)
{
return getDerived().VisitDecl(D);
}
bool WalkUpFromNamedDecl(NamedDecl *D)
{
if (!getDerived().WalkUpFromDecl(D))
return false;
if (!getDerived().VisitNamedDecl(D))
return false;
return true;
}
bool VisitNamedDecl(NamedDecl *D)
{
return true;
}
bool WalkUpFromLabelDecl(LabelDecl *D)
{
if (!getDerived().WalkUpFromNamedDecl(D))
return false;
if (!getDerived().VisitLabelDecl(D))
return false;
return true;
}
...
};

Traverse方法是入口点。 他们称之为WalkUpFrom方法,它们"走上"C++类层次结构和 为 AST 节点所在的类调用Visit方法 问题就是一个例子。

getDerived方法是 CRTP 机制的关键要素。 由 在其上调用方法,而不是直接this定义的任何方法 在Derived将(静态地)覆盖相应的基类 方法。

超类TraverseDecl方法执行基于以下 其参数的类型:

template <typename Derived>
bool RecursiveASTVisitor<Derived>::TraverseDecl(Decl *D)
{
...
switch (D->getKind()) {
...
case Decl::Label:
if (!getDerived().TraverseLabelDecl(static_cast<LabelDecl *>(D)))
return false;
break;
...
}
return true;
}

我选择专注于LabelDecl因为它是从抽象NamedDecl派生出来的具体类。 它的Traverse方法如下所示:

template <typename Derived>
bool RecursiveASTVisitor<Derived>::TraverseLabelDecl(LabelDecl *D)
{
bool ShouldVisitChildren = true;
bool ReturnValue = true;
if (!getDerived().shouldTraversePostOrder())
if (!getDerived().WalkUpFromLabelDecl(D))
return false;
if (ReturnValue && ShouldVisitChildren)
if (!getDerived().TraverseDeclContextHelper(dyn_cast<DeclContext>(D)))
return false;
if (ReturnValue) {
for (auto *I : D->attrs())
if (!getDerived().getDerived().TraverseAttr(I))
return false;
}
if (ReturnValue && getDerived().shouldTraversePostOrder())
if (!getDerived().WalkUpFromLabelDecl(D))
return false;
return ReturnValue;
}

这个想法是它在当前节点上调用WalkUpFrom,并Traverse在子节点上。 有两个调用WalkUpFrom因为 访客可以在预购或后购模式下操作。 再 所有调用都经过getDerived,因此可以覆盖。

宏元编程系统

回到RecursiveASTVisitor的定义,第一个 类的一部分直接在RecursiveASTVisitor.h.

但是然后我们到了头文件的这一部分,它声明了Decl层次结构的Traverse方法:

// ---- Methods on Decls ----
// Declare Traverse*() for all concrete Decl classes.
#define ABSTRACT_DECL(DECL)
#define DECL(CLASS, BASE) bool Traverse##CLASS##Decl(CLASS##Decl *D);
#include "clang/AST/DeclNodes.inc"
// The above header #undefs ABSTRACT_DECL and DECL upon exit.

DeclNodes.inc是在构建过程中生成的文件DeclNodes.td使用名为 TableGen 的工具。 在DeclNodes.inc中,我们发现(除许多其他事项外):

#ifndef NAMED
#  define NAMED(Type, Base) DECL(Type, Base)
#endif
ABSTRACT_DECL(NAMED(Named, Decl))
...
#ifndef LABEL
#  define LABEL(Type, Base) NAMED(Type, Base)
#endif
LABEL(Label, NamedDecl)

鉴于ABSTRACT_DECLDECL的定义似乎只是 在#include之前,我们看到ABSTRACT_DECL(NAMED(Named, Decl))扩展为无,并且:

LABEL(Label, NamedDecl)         expands to
NAMED(Label, NamedDecl)         which expands to
DECL(Label, NamedDecl)          which expands to
bool TraverseLabelDecl(LabelDecl *D);

接下来RecursiveASTVisitor.h我们将:

// Define WalkUpFrom*() and empty Visit*() for all Decl classes.
bool WalkUpFromDecl(Decl *D) { return getDerived().VisitDecl(D); }
bool VisitDecl(Decl *D) { return true; }
#define DECL(CLASS, BASE)                                                      
bool WalkUpFrom##CLASS##Decl(CLASS##Decl *D) {                               
TRY_TO(WalkUpFrom##BASE(D));                                               
TRY_TO(Visit##CLASS##Decl(D));                                             
return true;                                                               
}                                                                            
bool Visit##CLASS##Decl(CLASS##Decl *D) { return true; }
#include "clang/AST/DeclNodes.inc"

在具体定义了WalkUpFromDeclVisitDecl之后,这定义了 子类的WalkUpFromVisit。 我不会经历所有的 这些的宏扩展详细信息。

最后,TraverseLabelDecl来自此宏:

// This macro makes available a variable D, the passed-in decl.
#define DEF_TRAVERSE_DECL(DECL, CODE)                                          
template <typename Derived>                                                  
bool RecursiveASTVisitor<Derived>::Traverse##DECL(DECL *D) {                 
bool ShouldVisitChildren = true;                                           
bool ReturnValue = true;                                                   
if (!getDerived().shouldTraversePostOrder())                               
TRY_TO(WalkUpFrom##DECL(D));                                             
{ CODE; }                                                                  
if (ReturnValue && ShouldVisitChildren)                                    
TRY_TO(TraverseDeclContextHelper(dyn_cast<DeclContext>(D)));             
if (ReturnValue) {                                                         
/* Visit any attributes attached to this declaration. */                 
for (auto *I : D->attrs())                                               
TRY_TO(getDerived().TraverseAttr(I));                                  
}                                                                          
if (ReturnValue && getDerived().shouldTraversePostOrder())                 
TRY_TO(WalkUpFrom##DECL(D));                                             
return ReturnValue;                                                        
}

以及它的使用:

DEF_TRAVERSE_DECL(LabelDecl, {// There is no code in a LabelDecl.
})

其他Traverse方法在大括号内有(手写)代码 在子节点上调用Traverse

所有这些机制的结果是编译器看到TraverseWalkUpFrom和所有 AST 类的Visit方法,其中大部分 自动生成,但Doxygen看不到大部分。

最新更新