C++中的“X不是一种类型”错误

173

我有两个如下所示声明的类:

class User
{
public:
  MyMessageBox dataMsgBox;
};

class MyMessageBox
{
public:
  void sendMessage(Message *msg, User *recvr);
  Message receiveMessage();
  vector<Message> *dataMessageList;
};

当我尝试使用gcc编译它时,出现以下错误:

MyMessageBox未定义类型


28
我无数次遇到这个错误,才意识到IDE生成的import guards是重复的。 - Mazyod
3
请注意,如果您在.h / .hpp文件中定义类之前将extern引用放在声明之前,则还可能出现此错误,即使您在.cpp文件中的.h / .hpp包含后有实际声明也是如此。请注意不要改变原意,并尽可能使语言更通俗易懂。 - Owl
你应该始终使用命令 g++ 而不是 gcc 来编译 C++ 文件。 - Lorenzo Battilocchi
8个回答

255

当编译器编译类 User 并到达代码中的 MyMessageBox 行时,MyMessageBox 尚未定义。编译器不知道 MyMessageBox 的存在,因此无法理解您的类成员的含义。

在使用它作为成员之前,您需要确保 MyMessageBox 已经被定义了。这可以通过反转定义顺序来解决。但是,您会遇到循环依赖:如果您将 MyMessageBox 移至 User 上面,则在 MyMessageBox 的定义中,名称 User 将不会被定义!

您可以进行 前向声明 User;也就是声明它但不定义它。在编译期间,已声明但未定义的类型称为不完全类型。 考虑以下简单示例:

struct foo; // foo is *declared* to be a struct, but that struct is not yet defined

struct bar
{
    // this is okay, it's just a pointer;
    // we can point to something without knowing how that something is defined
    foo* fp; 

    // likewise, we can form a reference to it
    void some_func(foo& fr);

    // but this would be an error, as before, because it requires a definition
    /* foo fooMember; */
};

struct foo // okay, now define foo!
{
    int fooInt;
    double fooDouble;
};

void bar::some_func(foo& fr)
{
    // now that foo is defined, we can read that reference:
    fr.fooInt = 111605;
    fr.foDouble = 123.456;
}

通过前置声明 UserMyMessageBox 仍可以形成指向它的指针或引用:

class User; // let the compiler know such a class will be defined

class MyMessageBox
{
public:
    // this is ok, no definitions needed yet for User (or Message)
    void sendMessage(Message *msg, User *recvr); 

    Message receiveMessage();
    vector<Message>* dataMessageList;
};

class User
{
public:
    // also ok, since it's now defined
    MyMessageBox dataMsgBox;
};

你不能反过来这样做:如上所述,类成员需要有定义。(原因是编译器需要知道User占用多少内存,为了知道这一点,它需要知道其成员的大小。)如果您这样说:

你无法以相反的方式进行此操作:如前所述,一个类成员需要有定义。(原因是编译器需要知道User占用多少内存,而要知道这一点,它需要知道其成员的大小。)如果你说:

class MyMessageBox;

class User
{
public:
    // size not available! it's an incomplete type
    MyMessageBox dataMsgBox;
};

由于它还不知道大小,所以它无法工作。


另外一件事,这个函数:

 void sendMessage(Message *msg, User *recvr);

不应该使用指针来传递这两个参数。没有消息时不能发送消息,也没有接收者时不能发送消息。这两种情况都可以通过将 null 作为参数传递来表示(null 是一个完全有效的指针值!)

相反,应该使用引用(可能是 const 引用):

 void sendMessage(const Message& msg, User& recvr);

5
今天学到了新知识——我以为只要前向声明'MyMessageBox'就足够了。如果'MyMessageBox'还有一个类型为'User'的变量,那么会不会出现死锁情况呢? - Amarghosh
15
是的,这是不可能的。从逻辑上讲也是不可能的,因为“用户”将拥有一个“消息框”,而该“消息框”又将拥有一个“用户”,该“用户”又将拥有一个“消息框”,以此类推,会形成无限循环。 - GManNickG

12
  1. 提前声明用户(User)
  2. 将MyMessageBox的声明放在用户(User)之前

5
在我的情况下,事实证明这个非常无信息的错误是由循环依赖引起的。与其写成
// A.hpp
#include "B.hpp"

class A {
    B b;
}

// B.hpp
#include "A.hpp"

class B {
    A a;
}

我将第二个文件更改为

// B.hpp
class A;

class B {
    A a;
}

...所有的编译器错误以及我IDE中突然消失的语法高亮都消失了。

后来我确实需要在B.cpp文件的顶部添加一个#include "A.hpp",但这没关系,因为在这种情况下没有循环依赖(B.cpp -> A.hpp -> B.hpp -> /)。


这是我的问题。谢谢分享! - Mobi Zaman

4

C++编译器只处理一次输入。您使用的每个类必须先定义。您在定义之前使用了MyMessageBox。在这种情况下,您可以简单地交换两个类的定义。


交换不起作用,因为“MyMessageBox”在其方法声明中具有“User”类型。 - Amarghosh
实际上,该定义并没有使用类“User”。这是一个重要的区别,因为这意味着在那个点上只需要声明类User,而不需要定义它。但请参阅GMan的详细帖子。 - MSalters
是的,但仅仅交换定义是行不通的,因为“User”类型尚未声明。 - Amarghosh

3

顺便提一下,如果你有以下情况:

    class User; // let the compiler know such a class will be defined

    class MyMessageBox
    {
    public:
        User* myUser;
    };

    class User
    {
    public:
        // also ok, since it's now defined
        MyMessageBox dataMsgBox;
    };

那也可以,因为在MyMessageBox中,User被定义为一个指针。

1
前向声明是一个术语。 - impulse

3

在定义User之前需要定义MyMessageBox,因为User通过值包含了MyMessageBox对象(因此编译器应该知道其大小)。

此外,在定义MyMessageBox之前需要进行前向声明User,因为MyMessageBox包含了User*类型的成员。


2
您必须在使用之前声明它的原型:
class User;

class MyMessageBox
{
public:
 void sendMessage(Message *msg, User *recvr);
 Message receiveMessage();
 vector<Message> *dataMessageList;
};

class User
{
public:
 MyMessageBox dataMsgBox;
};

编辑:交换了类型


1
不行,无法工作。类成员必须被定义,而不能被前向声明。 - MSalters

1
在C++中,通常鼓励每个头文件只有一个类,可以参考SO中的这个讨论[1]。GManNickG的回答解释了为什么要这样做。但是,最好的解决方法是将User类放在一个头文件(User.h)中,将MyMessageBox类放在另一个头文件(MyMessageBox.h)中。然后,在你的User.h中包含MyMessageBox.h,在MyMessageBox.h中包含User.h。不要忘记使用“include guards”[2],以确保你的代码能够成功编译。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接