Ошибка «X не называет тип» в C ++

124

У меня объявлено два класса, как показано ниже:

class User
{
public:
  MyMessageBox dataMsgBox;
};

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

Когда я пытаюсь скомпилировать его с помощью gcc, появляется следующая ошибка:

MyMessageBox не называет тип

Ракеш К.
источник
17
Бесконечное количество раз я совершаю эту ошибку, просто чтобы понять, что средства защиты импорта, сгенерированные IDE, дублируются
Mazyod
1
Обратите внимание, что вы также можете получить эту ошибку, если поместите внешнюю ссылку на объявление в файле .h / .hpp до определения класса, даже если у вас есть фактическое объявление после включения .h / .hpp в .cpp файл.
Owl
Вы также всегда должны компилировать файлы C ++ с помощью команды, g++а неgcc
Лоренцо Баттилокки

Ответы:

205

Когда компилятор компилирует класс 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;
}

По вперед объявляя User, MyMessageBoxвсе еще может сформировать указатель или ссылку на него:

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занимает, и знать, что ему нужно знать размер своих членов.) Если бы вы сказали:

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);
GManNickG
источник
3
+1 Сегодня кое-что узнал - подумал, MyMessageBoxхватило бы форвардного декларирования . Что, если MyMessageBoxбы у переменной Userтоже был тип - это был бы тупик?
Amarghosh
14
@ Амаргош: Да, это невозможно. Логически невозможно , как хорошо, так как Userбудет иметь , MessageBoxкоторые имели бы User, что бы иметь , MessageBoxкоторые имели бы User, что бы иметь , MessageBoxкоторые имели бы User, что бы иметь , MessageBoxкоторый бы User...
GManNickG
7
  1. Вперед объявить пользователя
  2. Поместите объявление MyMessageBox перед пользователем
Брайан Р. Бонди
источник
3

Компиляторы C ++ обрабатывают свой ввод один раз. Каждый используемый вами класс должен быть определен первым. Вы используете, MyMessageBoxпрежде чем определять его. В этом случае вы можете просто поменять местами определения двух классов.

MSalters
источник
Перестановка не будет работать , как MyMessageBoxимеет Userтип в его объявлении метода.
Amarghosh
На самом деле это определение не использует класс User. Важное различие, потому что это означает, что класс User нужно только объявить в этой точке, а не определить . Но см. Обширный пост GMan.
MSalters
Да, но просто поменять местами определения не получится, поскольку Userтип еще не объявлен.
Amarghosh
3

Вам необходимо определить MyMessageBox перед пользователем, потому что пользователь включает объект MyMessageBox по значению (и поэтому компилятор должен знать его размер).

Также вам нужно будет переслать объявление User до MyMessageBox, потому что MyMessageBox включает члена типа User *.

Александр Полуэктов
источник
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 как указатель

awesomeamyg
источник
1
Форвардное заявление - это термин
benziv
1

Вы должны объявить его прототип перед его использованием:

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. Не забудьте «включить gaurds» [ 2 ], чтобы ваш код успешно компилировался.

Chehadeh
источник