你的头文件之间有循环引用。
-
如果Expression.h 在包含Variable.h 和Value.h 之前包含在翻译单元中,则Expression.h 将定义MATH_EXPRESSION_H 保护,然后在声明Expression 类之前包含Variable.h。 Variable.h 将再次包含Expression.h,由于MATH_EXPRESSION_H 保护,这实际上是一个无操作。因此,Variable 类将引用尚未声明的 Expression 类型,因此会出现编译器错误。
-
如果Variable.h 在包含Expression.h 之前包含在翻译单元中,则Variable.h 将定义MATH_VARIABLE_H 保护,然后在声明Variable 类之前包含Expression.h。 Expression.h 将再次包含Variable.h,由于MATH_VARIABLE_H 保护,这实际上是一个无操作。然后Expression.h 将包括Value.h,这将再次包括Expression.h,这是一个无操作(见上文)。因此,Value 类将引用尚未声明的 Expression 类型,因此会出现编译器错误。
-
如果Value.h 在包含Expression.h 之前包含在翻译单元中,则Value.h 将定义MATH_VALUE_H 保护,然后在声明Value 类之前包含Expression.h。 Expression.h 将包括Variable.h,这将再次包括Expression.h,这是一个无操作(见上文)。然后Expression.h 将包含Value.h,由于MATH_VALUE_H 保护,这是一个无操作。因此,Variable 类将引用尚未声明的 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!
...
};
因此,无论首先包含哪个标头,这都是一种双赢的局面。
由于您在函数参数类型和返回类型中仅使用指向各种类的指针,而不是访问类的任何成员,您可以通过将 Expression.h 中的 #include 语句替换为 @987654321 来解决这个循环问题@ 而不是,例如:
#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!
...
};
这确实意味着您必须在任何其他需要实际访问Variable 和Value 成员的源/头文件中的#include Variable.h 和Value.h 头文件。
每当您处理仅使用指向类型的指针/引用而不使用该类型的任何成员的声明时,您应该更喜欢该类型的前向声明,而不是头文件包含。这将减轻编译器的负担,并避免任何潜在的循环问题。