为什么我的 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.h
和 Value.h
之前包含在翻译单元中,则 Expression.h
将定义 MATH_EXPRESSION_H
守卫,然后在声明 Expression
class 之前包含 Variable.h
。 Variable.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
的声明只需要知道Variable
和Value
存在,而不需要知道它们里面长什么样子。与 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.h
和 Value.h
header 任何其他需要实际访问的 source/header 文件Variable
和 Value
.
的成员
每当你处理只对一个类型使用 pointers/references 而不是该类型的任何成员的声明时,你应该更喜欢该类型的前向声明,而不是 header 文件包含。这将减轻编译器的负担,并避免任何潜在的循环问题。
你好 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.h
和Value.h
之前包含在翻译单元中,则Expression.h
将定义MATH_EXPRESSION_H
守卫,然后在声明Expression
class 之前包含Variable.h
。Variable.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
的声明只需要知道Variable
和Value
存在,而不需要知道它们里面长什么样子。与 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.h
和 Value.h
header 任何其他需要实际访问的 source/header 文件Variable
和 Value
.
每当你处理只对一个类型使用 pointers/references 而不是该类型的任何成员的声明时,你应该更喜欢该类型的前向声明,而不是 header 文件包含。这将减轻编译器的负担,并避免任何潜在的循环问题。