У C ++ помилка "X не називає тип"


123

У мене є два класи, оголошені як нижче:

class User
{
public:
  MyMessageBox dataMsgBox;
};

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

Коли я намагаюся скомпілювати його за допомогою gcc, він видає таку помилку:

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


16
Нескінченний раз я виходжу з цієї помилки, просто зрозумівши, що захисні пристрої імпорту, згенеровані IDE, дублюються
Mazyod

1
Зауважте, що ви також можете отримати цю помилку, якщо розмістити зовнішнє посилання на декларацію у файл .h / .hpp до того, як буде визначено клас, навіть якщо у вас є фактичне оголошення після включення .h / .hpp в .cpp файл.
Сова

Ви також завжди повинні компілювати файли C ++ з командою, g++а неgcc
Лоренцо Баттілоччі

Відповіді:


203

Коли компілятор компілює клас 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);

3
+1 Навчився чогось сьогодні - я думав, що вперед заявити MyMessageBoxбуде достатньо. Що робити, якщо також MyMessageBoxбула змінна типу User- це був би тупик?
Amarghosh

14
@Amargosh: Так, це було б неможливо. Логічно також неможливо, оскільки Userмав би, MessageBoxякий мав би User, який мав би, MessageBoxякий мав би User, який мав би, MessageBoxякий мав би User, який мав би, MessageBoxякий мав би User...
GManNickG


3

Компілятори C ++ обробляють свій вклад один раз. Кожен клас, який ви використовуєте, повинен бути визначений спочатку. Ви використовуєте, MyMessageBoxперш ніж визначити це. У цьому випадку можна просто поміняти два визначення класу.


Перестановка не працюватиме , як MyMessageBoxмає Userтип в його оголошенні методу.
Amarghosh

Насправді це визначення не використовує клас User. Важлива відмінність, тому що це означає, що клас користувача повинен бути оголошений лише в той момент, а не визначений . Але дивіться широкий пост GMan.
MSalters

Так, але проста заміна визначень не працюватиме, оскільки Userтип ще не оголошений.
Amarghosh

3

Ви повинні визначити MyMessageBox перед користувачем - тому що користувач включає об'єкт MyMessageBox за значенням (і тому компілятор повинен знати його розмір).

Також вам потрібно буде переслати заяву користувача для 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 як вказівник


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. Не забувайте "включати гаурди" [ 2 ], щоб ваш код успішно збирався .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.