简体   繁体   中英

Why am I getting base class undefined?

Hello StackOverflow!

I'm trying to make some kind of algebraic solving code. I've laid out some basic structure. But I stumble uppon the error that a subclass finds the base-class being undefined. I've tried removing #pragma once and removing and changing include statements here and there. But I cannot figure it out.

I think it has something to do with the files including eachother, but from what I thought I knew from before. That is supposed to be possible thanks to #pragma once and #ifndef . Please advice me on what I am doing wrong here!

Expression.h:

#pragma once
#ifndef MATH_EXPRESSION_H
#define MATH_EXPRESSION_H

#include <string>

#include "Variable.h"
#include "Value.h"

class Expression {

public:
    virtual             Expression*     eval(const Variable* var, Value* val)           = 0;
    virtual operator    std::string()                                           const   = 0;

    template<class T>
    bool                isType() {
        return dynamic_cast<T*>(this);
    }

};

#endif

Variable.h:

#pragma once
#ifndef MATH_VARIABLE_H
#define MATH_VARIABLE_H

#include "Expression.h"

class Variable : public Expression {
};

#endif

Value.h:

#pragma once
#ifndef MATH_VALUE_H
#define MATH_VALUE_H

#include "Expression.h"

class Value : public Expression {

public:
    float value;

    Value(float value) : value(value) {}

public:
    Expression* eval() { return this; }
    Expression* eval(const Variable* v, Value* val) {
        return this->eval();
    }

    operator std::string() const {
        return std::to_string(value);
    }
};

#endif

And this is the build error I am trying to solve:

Variable.h(7,36): error C2504: 'Expression': base class undefined

You have circular references between your header files.

  • If Expression.h is included in a translation unit before either Variable.h and Value.h are included, Expression.h will define the MATH_EXPRESSION_H guard and then include Variable.h before declaring the Expression class. Variable.h will include Expression.h again, which is effectively a no-op because of the MATH_EXPRESSION_H guard. So, the Variable class will be referring to an Expression type that hasn't been declared yet, hence a compiler error.

  • If Variable.h is included in a translation unit before Expression.h is included, Variable.h will define the MATH_VARIABLE_H guard and then include Expression.h before declaring the Variable class. Expression.h will include Variable.h again, which is effectively a no-op because of the MATH_VARIABLE_H guard. Expression.h will then include Value.h , which will include Expression.h again, which is a no-op (see above). So, the Value class will be referring to an Expression type that hasn't been declared yet, hence a compiler error.

  • If Value.h is included in a translation unit before Expression.h is included, Value.h will define the MATH_VALUE_H guard and then include Expression.h before declaring the Value class. Expression.h will include Variable.h , which will include Expression.h again, which is a no-op (see above). Then Expression.h will include Value.h , which is a no-op due to the MATH_VALUE_H guard. So, the Variable class will be referring to an Expression type that hasn't been declared yet, hence a compiler error.

Let's look at this with actual code.

If Expression.h is included first, this is what the compiler will see after the preprocessor directives have been processed:

// content from <string> ...

class Variable : public Expression { // <-- error, Expression not defined!
};

class Value : public Expression { // <-- error, Expression not defined!
   ...
public:
    Expression* eval() { ... } // <-- error, Expression not defined!
    Expression* eval(const Variable* v, Value* val) { ... } // <-- error, Expression not defined!
    ...
};

class Expression {
public:
    virtual Expression* eval(const Variable* var, Value* val) = 0; // <-- OK!
};

If Variable.h is included first:

// content from <string> ...

class Value : public Expression { // <-- error, Expression not defined!
   ...
public:
    Expression* eval() { ... } // <-- error, Expression not defined!
    Expression* eval(const Variable* v, Value* val) { ... } // <-- error, Expression and Variable not defined!
    ...
};

class Expression {
public:
    virtual Expression* eval(const Variable* var, Value* val) = 0; // <-- error, Variable not defined!
    ...
};

class Variable : public Expression { // <-- OK!
};

If Value.h is included first:

// content from <string> ...

class Variable : public Expression { // <-- error, Expression not defined!
};

class Expression {
public:
    virtual Expression* eval(const Variable* var, Value* val) = 0; // <-- error, Value not defined!
    ...
};

class Value : public Expression { // <-- OK!
public:
    Expression* eval() { ... } // <-- OK!
    Expression* eval(const Variable* v, Value* val) { ... } // <-- OK!
    ...
};

So, this is a no-win situation regardless of which header is included first.

Since you are using only pointers to the various classes in function parameter types and return types, and not accessing any members of the classes, you can break this circular issue by replacing the #include statements in Expression.h with forward declarations instead, eg:

#pragma once
#ifndef MATH_EXPRESSION_H
#define MATH_EXPRESSION_H

#include <string>

// forward declarations
class Variable;
class Value;

class Expression {

public:
    virtual Expression* eval(const Variable* var, Value* val) = 0;
    virtual operator std::string() const = 0;

    template<class T>
    bool isType() {
        return dynamic_cast<T*>(this);
    }

};

#endif

The declaration of Expression only needs to know that Variable and Value exist, not what they look like inside. Same with the declaration of Value only needing to know that Variable exists, not what it looks like inside.

So, let's look at what this change does to the code that the compiler sees after the preprocessor directives are processed.

If Expression.h is included first:

// content from <string> ...

// forward declarations
class Variable;
class Value;

class Expression {
public:
    virtual Expression* eval(const Variable* var, Value* val) = 0; // <-- OK!
    ...
};

If Variable.h is included first:

// content from <string> ...

// forward declarations
class Variable;
class Value;

class Expression {
public:
    virtual Expression* eval(const Variable* var, Value* val) = 0; // <-- OK!
};

class Variable : public Expression { // OK!
};

If Value.h is included first:

// content from <string> ...

// forward declarations
class Variable;
class Value;

class Expression {
public:
    virtual Expression* eval(const Variable* var, Value* val) = 0; // <-- OK!
    ...
};

class Value : public Expression { // <-- OK!
    ...
public:
    Expression* eval() { ... } // <-- OK!
    Expression* eval(const Variable* v, Value* val) { ... } // <-- OK!
    ...
};

This does mean that you will have to #include the Variable.h and Value.h headers in any other source/header files that need to actually access the members of Variable and Value .

Whenever you are dealing with declarations that only use pointers/references to a type and not any members of that type, you should prefer a forward declaration for that type, not a header file include. This will ease the burden of the compiler, and avoid any potential circular issues.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM