【问题标题】:Why am I getting base class undefined?为什么我得到未定义的基类?
【发布时间】:2021-03-24 00:54:48
【问题描述】:

你好 StackOverflow!

我正在尝试制作某种代数求解代码。我已经列出了一些基本结构。但是我偶然发现子类发现基类未定义的错误。我试过删除#pragma once 并在这里和那里删除和更改包含语句。但我想不通。

我认为这与包括彼此在内的文件有关,但从我认为我以前知道的情况来看。感谢#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

变量.h:

#pragma once
#ifndef MATH_VARIABLE_H
#define MATH_VARIABLE_H

#include "Expression.h"

class Variable : public Expression {
};

#endif

值.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

【问题讨论】:

  • 您有循环包含。那是行不通的。从Expression.h 中删除#include "Variable.h"#include "Value.h",用前向声明替换它们。
  • 好的,但是如果我开始只包含 Varaible.h: ´#include "Expression.h"` #include &lt;string&gt; /* #include "Variable.h" !!! Shouldn't proceed becuse we already included it*/ @ 为什么不是结果987654333@class Expression { ... }class Variable { ... }

标签: c++ include undefined subclass base-class


【解决方案1】:

你的头文件之间有循环引用。

  • 如果Expression.h 在包含Variable.hValue.h 之前包含在翻译单元中,则Expression.h 将定义MATH_EXPRESSION_H 保护,然后在声明Expression 类之前包含Variable.hVariable.h 将再次包含Expression.h,由于MATH_EXPRESSION_H 保护,这实际上是一个无操作。因此,Variable 类将引用尚未声明的 Expression 类型,因此会出现编译器错误。

  • 如果Variable.h 在包含Expression.h 之前包含在翻译单元中,则Variable.h 将定义MATH_VARIABLE_H 保护,然后在声明Variable 类之前包含Expression.hExpression.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.hExpression.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 的声明只需要知道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!
    ...
};

这确实意味着您必须在任何其他需要实际访问VariableValue 成员的源/头文件中的#include Variable.hValue.h 头文件。

每当您处理仅使用指向类型的指针/引用而不使用该类型的任何成员的声明时,您应该更喜欢该类型的前向声明,而不是头文件包含。这将减轻编译器的负担,并避免任何潜在的循环问题。

【讨论】:

    猜你喜欢
    • 2019-10-31
    • 1970-01-01
    • 1970-01-01
    • 2021-12-30
    • 2015-03-23
    • 2012-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多