Błąd „X nie nazywa typu” w C ++

124

Mam dwie klasy zadeklarowane jak poniżej:

class User
{
public:
  MyMessageBox dataMsgBox;
};

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

Kiedy próbuję skompilować go za pomocą gcc, daje następujący błąd:

MyMessageBox nie nazywa typu

Rakesh K
źródło
17
Niekończące się czasy, gdy idę ten błąd, tylko po to, aby zdać sobie sprawę, że zabezpieczenia importu generowane przez IDE są zduplikowane
Mazyod
1
Zwróć uwagę, że możesz również uzyskać ten błąd, jeśli umieścisz odwołanie zewnętrzne do deklaracji w pliku .h / .hpp przed zdefiniowaniem klasy, nawet jeśli masz rzeczywistą deklarację po włączeniu .h / .hpp w .cpp plik.
Sowa
Powinieneś także zawsze kompilować pliki C ++ za pomocą polecenia, g++a niegcc
Lorenzo Battilocchi

Odpowiedzi:

205

Kiedy kompilator kompiluje klasę Useri przechodzi do MyMessageBoxlinii, MyMessageBoxnie została jeszcze zdefiniowana. Kompilator nie ma pojęcia, że MyMessageBoxistnieje, więc nie może zrozumieć znaczenia elementu członkowskiego twojej klasy.

Musisz upewnić się, że MyMessageBoxjest zdefiniowany, zanim użyjesz go jako członka. Można to rozwiązać, odwracając kolejność definicji. Masz jednak cykliczną zależność: jeśli przejdziesz MyMessageBoxpowyżej User, to w definicji MyMessageBoxnazwy Usernie zostanie zdefiniowana!

Możesz tylko zadeklarować User ; to znaczy zadeklaruj, ale nie definiuj. Podczas kompilacji typ, który jest zadeklarowany, ale nie zdefiniowany, jest nazywany niekompletnym typem . Rozważmy prostszy przykład:

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;
}

Deklarując do przodu User, MyMessageBoxnadal może tworzyć wskaźnik lub odniesienie do niego:

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;
};

Nie możesz tego zrobić na odwrót: jak wspomniano, członek klasy musi mieć definicję. (Powodem jest to, że kompilator musi wiedzieć, ile pamięci Userzajmuje i wiedzieć, że musi znać rozmiar jej członków.) Gdybyś miał powiedzieć:

class MyMessageBox;

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

Nie zadziała, ponieważ nie zna jeszcze rozmiaru.


Na marginesie, ta funkcja:

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

Prawdopodobnie nie powinien brać żadnego z nich za pomocą wskaźnika. Nie możesz wysłać wiadomości bez wiadomości, ani nie możesz wysłać wiadomości bez użytkownika, do którego można ją wysłać. Obie te sytuacje można wyrazić, przekazując null jako argument do któregokolwiek z parametrów (null to doskonale poprawna wartość wskaźnika!)

Zamiast tego użyj odwołania (prawdopodobnie const):

 void sendMessage(const Message& msg, User& recvr);
GManNickG
źródło
3
+1 Nauczyłem się czegoś dzisiaj - pomyślałem, że zadeklarowanie w MyMessageBoxprzyszłości wystarczyłoby. A gdyby MyMessageBoxtak też była zmienna typu User- czy to byłby impas?
Amarghosh
14
@Amargosh: Tak, byłoby to niemożliwe. Logicznie niemożliwe, ponieważ Usermiałoby a, MessageBoxktóre miałoby a User, które miałoby a, MessageBoxktóre miałoby a User, które miałoby a, MessageBoxktóre miałoby a User, które miałoby a, MessageBoxktóre miałoby User...
GManNickG
7
  1. Przekaż deklarację użytkownika
  2. Umieść deklarację MyMessageBox przed użytkownikiem
Brian R. Bondy
źródło
3

Kompilatory C ++ przetwarzają dane wejściowe raz. Każda używana klasa musi zostać zdefiniowana jako pierwsza. Używasz MyMessageBoxzanim go zdefiniujesz. W takim przypadku możesz po prostu zamienić dwie definicje klas.

MSalters
źródło
Zamiana nie będzie działać, ponieważ MyMessageBoxma Usertyp w deklaracji metody.
Amarghosh
Właściwie ta definicja nie używa klasy User. Ważne rozróżnienie, ponieważ oznacza to, że klasa User musi być tylko zadeklarowana w tym miejscu, a nie zdefiniowana . Ale zobacz obszerny post GMan.
MSalters
Tak, ale zwykła zamiana definicji nie zadziała, ponieważ Usertyp nie został jeszcze zadeklarowany.
Amarghosh
3

Musisz zdefiniować MyMessageBox przed User - ponieważ użytkownik zawiera obiekt MyMessageBox według wartości (a więc kompilator powinien znać jego rozmiar).

Będziesz także musiał przekazać dalej deklarację użytkownika przed MyMessageBox - ponieważ MyMessageBox zawiera element członkowski typu User *.

Alexander Poluektov
źródło
3

A propos, gdybyś miał:

    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;
    };

Wtedy to również zadziała, ponieważ użytkownik jest zdefiniowany w MyMessageBox jako wskaźnik

awesomeamyg
źródło
1
Deklaracja forward to termin
benziv
1

Przed użyciem musisz zadeklarować prototyp:

class User;

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

class User
{
public:
 MyMessageBox dataMsgBox;
};

edycja : Zamieniono typy

Alex LE
źródło
1
Nie, nie zadziała. Składowe klasy muszą być zdefiniowane, a nie deklarowane w przód.
MSalters
1

W C ++ jest zawsze zalecane, abyś miał jedną klasę na plik nagłówkowy, zobacz tę dyskusję w SO [ 1 ]. Odpowiedzi GManNickG mówią, dlaczego tak się dzieje. Ale najlepszym sposobem rozwiązania tego problemu jest umieszczenie Userklasy w jednym pliku nagłówkowym ( User.h), a MyMessageBoxklasy w innym pliku nagłówkowym ( MyMessageBox.h). Następnie User.hwłączasz MyMessageBox.hi MyMessageBox.hwłączasz User.h. Nie zapomnij o „include gaurds” [ 2 ], aby Twój kod się pomyślnie skompilował.

Chehadeh
źródło