Search code examples
c++vtablevisitor-pattern

Undefined Reference to 'vtable for class'


I am implementing a Visitor class in C++ that generates XML output for a parse tree.

When I compile with Clion on Windows the code compiles but when it runs after it outputs what is expected it crashes. The error code is this

Process finished with exit code -1073741819 (0xC0000005)

When I try to compile using gcc (without Clion) I get the error message

Undefined Reference to 'vtable for PrintXMLVisitor'.

My code is the following. I have distilled it down to the least amount the produces the error

ASTNode.h

#ifndef MINILANG_ASTNODE_H
#define MINILANG_ASTNODE_H

#include <memory>
class Visitor;

class ASTNode {
public:
    virtual void accept(std::shared_ptr<Visitor> visitor) = 0;
};


#endif //MINILANG_ASTNODE_H

ASTTypeNode.h

#ifndef MINILANG_ASTTYPENODE_H
#define MINILANG_ASTTYPENODE_H


#include "ASTNode.h"

class ASTTypeNode: public ASTNode {
public:
    enum Type {Real, Int, Bool, String};
    ASTTypeNode(Type type);
    Type getType() const;

    void accept(std::shared_ptr<Visitor> visitor) override;

private:
    Type type;
};


#endif //MINILANG_ASTTYPENODE_H

ASTTypeNode.cpp

#include "ASTTypeNode.h"
#include "Visitor.h"

ASTTypeNode::ASTTypeNode(ASTTypeNode::Type type)
    : type(type)
{

}

ASTTypeNode::Type ASTTypeNode::getType() const {
    return type;
}

void ASTTypeNode::accept(std::shared_ptr<Visitor> visitor) {
    visitor->visit(std::shared_ptr<ASTTypeNode>(this));
}

Visitor.h

#ifndef MINILANG_VISITOR_H
#define MINILANG_VISITOR_H

#include <memory>
#include "ASTTypeNode.h"


class Visitor {
public:
    virtual void visit(std::shared_ptr<ASTTypeNode> typeNode) = 0;
};


#endif //MINILANG_VISITOR_H

PrintXMLVisitor.h

#ifndef MINILANG_PRINTXMLVISITOR_H
#define MINILANG_PRINTXMLVISITOR_H


#include "Visitor.h"

class PrintXMLVisitor: public Visitor {
public:
    void visit(std::shared_ptr<ASTTypeNode> typeNode) override;
};


#endif //MINILANG_PRINTXMLVISITOR_H

PrintXMLVisitor.cpp

#include "PrintXMLVisitor.h"
#include <iostream>

void PrintXMLVisitor::visit(std::shared_ptr<ASTTypeNode> typeNode) {

    std::string typeName;
    switch(typeNode->getType())
    {
        case ASTTypeNode::Type::Real:
            typeName = "Real";
            break;
        case ASTTypeNode::Type::Int:
            typeName = "Int";
            break;
        case ASTTypeNode::Type::Bool:
            typeName = "Bool";
            break;
        case ASTTypeNode::Type::String:
            typeName = "String";
            break;
        default:
            typeName = "Error";
            exit(22);
    }

    std::cout << "<TypeNode>" << typeName << "</TypeNode>" << std:: endl;
}

main.cpp

#include <iostream>
#include "Lexer.h"
#include "ASTTypeNode.h"
#include "PrintXMLVisitor.h"

int main() {

    ASTTypeNode astTypeNode (ASTTypeNode::Type::Int);
    astTypeNode.accept(std::make_shared<PrintXMLVisitor>());


    return 0;
}

Solution

  • Your crafting a shared pointer that isn't dynamic. Specifically,

    void ASTTypeNode::accept(std::shared_ptr<Visitor> visitor) {
        visitor->visit(std::shared_ptr<ASTTypeNode>(this)); // <=== HERE
    }
    

    The this in that statement refers to:

    int main() 
    {
        ASTTypeNode astTypeNode (ASTTypeNode::Type::Int); // <== this object
        astTypeNode.accept(std::make_shared<PrintXMLVisitor>());
        return 0;
    }
    

    Changing toolchains isn't going to fix this problem you have options, the two most obvious being:

    • Stop using a std::shared_ptr for the visit parameter.
    • Manage all ASTNodeType instances a requiring being std::shared_ptr managed and share from this using the std:enable_shared_from_this capabilities of the standard library.

    The former of these is obvious (or at least it is now), so I'll not discuss it further. The latter is not necessarily trivial, as it mandates any instances of your underlying class that utilize shared_from_this must be managed by std::shared_ptr wrappers. I.e., there are no concrete constructions like you're currently doing in main(). This could have significant impact on your overall code base, so choose this carefully.

    An example of how the above would work in your case:

    First, change the derivation chain of ASTNodeType to look like this:

    class ASTTypeNode
        : public ASTNode
        , public std::enable_shared_from_this<ASTTypeNode> // ADDED
    

    Next, utilize shared_from_this as follows:

    void ASTTypeNode::accept(std::shared_ptr<Visitor> visitor) 
    {
        visitor->visit(shared_from_this()); // HERE
    }
    

    And finally, honor the warrant you've made that ASTNodeType instances are shared-ptr managed by doing this:

    int main() 
    {
        std::shared_ptr<ASTTypeNode> astTypeNode = std::make_shared<ASTTypeNode>(ASTTypeNode::Type::Int);
        astTypeNode->accept(std::make_shared<PrintXMLVisitor>());
        return 0;
    }
    

    That should work. Read more about the things used in the above code here:

    As I said, all of this is to facilitate using a std::shared_ptr from an object given only a this pointer. If you can remove that requirement in the first place, it may be an easier path to take, and I would consider that first.