For the following code
struct X {
int a;
};
int main() {
X x;
return 0;
}
The clang AST does not show a DestructorDecl
:
CXXRecordDecl 0x55a415f54f00 </home/gkxx/exercises/smfgen/tmp/../tmp/a.cpp:1:1, line:4:1> line:1:8 referenced struct X definition
|-DefinitionData pass_in_registers aggregate standard_layout trivially_copyable pod trivial literal
| |-DefaultConstructor exists trivial
| |-CopyConstructor simple trivial has_const_param implicit_has_const_param
| |-MoveConstructor exists simple trivial
| |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param
| |-MoveAssignment exists simple trivial needs_implicit
| `-Destructor simple irrelevant trivial needs_implicit
|-CXXRecordDecl 0x55a415f55018 <col:1, col:8> col:8 implicit struct X
|-FieldDecl 0x55a415f550c0 <line:2:3, col:7> col:7 a 'int'
|-CXXConstructorDecl 0x55a415f55320 <line:1:8> col:8 implicit used X 'void () noexcept' inline default trivial
| `-CompoundStmt 0x55a415f557d8 <col:8>
|-CXXConstructorDecl 0x55a415f55458 <col:8> col:8 implicit constexpr X 'void (const X &)' inline default trivial noexcept-unevaluated 0x55a415f55458
| `-ParmVarDecl 0x55a415f55568 <col:8> col:8 'const X &'
`-CXXConstructorDecl 0x55a415f55618 <col:8> col:8 implicit constexpr X 'void (X &&)' inline default trivial noexcept-unevaluated 0x55a415f55618
`-ParmVarDecl 0x55a415f55728 <col:8> col:8 'X &&'
I tried to get the CXXRecordDecl
of struct X
and found that decl->getDestructor()
returns nullptr
.
This makes me a little bit confused, because the standard says that a class without a user-declared destructor will have an implicilty-declared one, and it will be implicitly-defined when odr-used. So what is an odr-used destructor? Is the destructor of this X
odr-used? Or is this just about my misunderstanding of clang AST and the behavior of clang::CXXRecordDecl::getDestructor
?
Note that in contrast, the clang-AST shows that the default constructor of X
is defined, even though it does not do anything.
Yes. Quoting basic.def.odr:
A destructor for a class is odr-used if it is potentially invoked.
where "potentially invoked" has a non-trivial definition, but in short,
is true here because main()
declares an object of type X
.
getDestructor()
is nullptr
?The documentation of
CXXRecordDecl::getDestructor()
just says:
Returns the destructor decl for this class.
without even acknowledging that it can return nullptr
, let alone
explaining what that would mean. Based on reading the source, I
conclude it means the destructor is trivial, and that none of the
conditions that would cause an implicit declaration to be created anyway
(see below) have been satisfied.
It does not mean that Clang is claiming the destructor is not
ODR-used. The
Decl::isUsed()
method claims to report this information (see documentation on
setIsUsed()
), but it does not appear to be completely accurate; even
if I cause the destructor declaration to be created by adding a
virtual
function, the destructor is still not marked isUsed()
.
Yes--but to be conforming, Clang only needs to produce compiled output that acts "as if" the destructor had been defined. The absence of a particular AST node doesn't make it non-conforming. Even the lack of a definition in the compiled object file doesn't, so long as it adheres to the relevant ABI, thereby cooperating with other tools, to again achieve the required "as if" behavior (under the assumption that the source code complies with the One Definition Rule).
This doesn't appear to be documented, so I tried to figure it out from
the source. An implicit destructor declaration is created by
Sema::DeclareImplicitDestructor()
at
SemaDeclCXX.cpp:13803
.
This function is called in several places, and I didn't follow all of
the chains backward, but to a first approximation, this will not be
called if:
requires
).Note that it is that last condition, overload resolution due to the
declaration of x
, that causes Clang to declare the three implicit
constructors.
Yes! You can call
Sema::ForceDeclarationOfImplicitMembers
on a CXXRecordDecl
to force Clang to declare the implicit members even
if it would not have done so otherwise. For instance, right after
parsing, you can use a
RecursiveASTVisitor
to walk the AST and call this method on every CXXRecordDecl
.
After doing that, all of the implicit members will be available.
Sema
object?You cannot get it from an ASTContext
because that is just the AST,
whereas Sema
is part of the process for creating the AST. (If you
were to save the AST to disk and then load it back, there would not be a
Sema
object at all.)
My preferred approach is to use
ASTUnit::LoadFromCompilerInvocation
to do the initial parse. That yields an ASTUnit
object immediately,
from which you can get both the Sema
(via getSema()
) and the
ASTContext
(via getASTContext()
).
If you are instead using
ClangTool::run
,
which is what the
tutorial
points people at:
Tool.run(
clang::tooling::newFrontendActionFactory<MyASTFrontendAction>().get());
Grab the CompilerInstance
in CreateASTConsumer
:
class MyASTFrontendAction : public clang::ASTFrontendAction {
public:
virtual unique_ptr<clang::ASTConsumer> CreateASTConsumer(
clang::CompilerInstance &ci,
llvm::StringRef inFile) override
{
return make_unique<MyASTConsumer>(ci);
}
};
Then store it in the ASTConsumer
, and use its getSema()
when needed:
class MyASTConsumer : public clang::ASTConsumer {
public:
clang::CompilerInstance &m_compilerInstance;
MyASTConsumer(clang::CompilerInstance &compilerInstance)
: m_compilerInstance(compilerInstance)
{}
virtual void HandleTranslationUnit(clang::ASTContext &Context) override
{
clang::Sema &sema = m_compilerInstance.getSema();
// ...
}
};