为什么我的 base class 未定义?

Why am I getting base class undefined?

你好 Whosebug!

我正在尝试制作某种代数求解代码。我已经列出了一些基本结构。但是我偶然发现了 subclass 发现 base-class 未定义的错误。我试过删除 #pragma once 并在各处删除和更改 include 语句。但是我想不通。

我认为这与包含海誓山盟的文件有关,但据我所知我以前知道。由于 #pragma once#ifndef,这应该是可能的。请告诉我我在这里做错了什么!

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

这是我要解决的构建错误:

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

您的 header 个文件之间存在循环引用。

  • 如果 Expression.h 在包含 Variable.hValue.h 之前包含在翻译单元中,则 Expression.h 将定义 MATH_EXPRESSION_H 守卫,然后在声明 Expression class 之前包含 Variable.hVariable.h 将再次包含 Expression.h,这实际上是一个 no-op,因为 MATH_EXPRESSION_H 守卫。因此,Variable class 将引用尚未声明的 Expression 类型,因此会出现编译器错误。

  • 如果 Variable.h 在包含 Expression.h 之前包含在翻译单元中,则 Variable.h 将定义 MATH_VARIABLE_H 守卫,然后包含 Expression.h 在声明 Variable class 之前。 Expression.h 将再次包含 Variable.h,这实际上是一个 no-op,因为 MATH_VARIABLE_H 守卫。 Expression.h 然后将包括 Value.h,这将再次包括 Expression.h,这是一个 no-op(见上文)。因此,Value class 将引用尚未声明的 Expression 类型,因此会出现编译器错误。

  • 如果 Value.h 在包含 Expression.h 之前包含在翻译单元中,则 Value.h 将定义 MATH_VALUE_H 守卫,然后包含 Expression.h 在声明 Value class 之前。 Expression.h 将包含 Variable.h,后者将再次包含 Expression.h,即 no-op(见上文)。然后 Expression.h 将包括 Value.h,由于 MATH_VALUE_H 守卫,这是一个 no-op。因此,Variable class 将引用尚未声明的 Expression 类型,因此会出现编译器错误。

让我们用实际代码来看一下。

如果首先包含 Expression.h,这是编译器在处理预处理器指令后看到的内容:

// 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!
};

如果先包含Variable.h:

// 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!
};

如果先包含Value.h:

// 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!
    ...
};

因此,无论首先包含哪个 header,这都是 no-win 情况。

由于您仅使用指向函数参数类型和 return 类型中的各种 classes 的指针,而不访问 classes 的任何成员,因此您可以打破这个通过将 Expression.h 中的 #include 语句替换为 forward declarations 而不是循环问题,例如:

#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

Expression的声明只需要知道VariableValue存在,而不需要知道它们里面长什么样子。与 Value 的声明相同,只需要知道 Variable 存在,而不是它里面的样子。

那么,让我们看看这个更改对编译器在处理预处理器指令后看到的代码有何影响。

如果先包含Expression.h:

// content from <string> ...

// forward declarations
class Variable;
class Value;

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

如果先包含Variable.h:

// 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!
};

如果先包含Value.h:

// 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!
    ...
};

这意味着您必须 #include Variable.hValue.h header 任何其他需要实际访问的 source/header 文件VariableValue.

的成员

每当你处理只对一个类型使用 pointers/references 而不是该类型的任何成员的声明时,你应该更喜欢该类型的前向声明,而不是 header 文件包含。这将减轻编译器的负担,并避免任何潜在的循环问题。