Ошибка функции друга: «B» не был объявлен

Я протестировал следующий код (просто для проверки, я знаю, что это плохая идея), и я не мог понять, почему в нем ошибка. Пожалуйста, помогите мне!

Есть два класса A и B, оба в отдельных hpp файлах. Я позволил им включать друг друга (я думаю, это не имеет значения из-за include guard), и это не пошло не так, пока я не добавил определение B::fun в B.cpp.

А.ч

#ifndef A_H
#define A_H

#include "B.h"

class A {
    friend void B::fun(A&);
};

#endif

Б.ч

#ifndef B_H
#define B_H

#include "A.h"

class A;

class B {
    public:
        void fun(A&);
};
#endif

B.cpp

#include "B.h"

void B::fun(A& a) {
    
}

main.cpp

#include "A.h"
#include "B.h"

int main() {

}

Я запускаю его с помощью такой команды:

g++ -g -o test main.cpp B.cpp

Сообщение об ошибке

A.h:7:17: error: 'B' has not been declared
    friend void B::fun(A&);

Я знаю два обходных пути для устранения симптомов, но я хочу понять основную причину проблемы.

Способ 1: прокомментируйте код #include "A.h" в B.h

Способ 2: Измените #include "B.h" на #include "A.h" в B.cpp

Я хочу знать, почему эти обходные пути избегают симптомов, точную причину обоих методов.

Я отредактировал, чтобы подчеркнуть разницу между «знанием решения» и «наличием обходных путей без понимания». Я думаю, что это делает вопрос более ясным. Если вам это не нравится, не стесняйтесь отменить мою правку, и я оставлю ее. Удачи.

Yunnosch 26.12.2020 09:01

Первый «обходной путь» - это путь (фактически удалить вместо комментария ;-)). Второй — это просто обходной путь, позволяющий снова появиться проблеме с дополнительными файлами.

Jarod42 26.12.2020 11:35
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
2
108
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Просто удалите #include "A.hpp" из B.hpp. Это происходит потому, что когда B.cpp включает B.hpp, последний, в свою очередь, включает A.hpp до того, как B будет определено. Но вам вообще не нужно включать A.hpp, потому что вы правильно объявили class A.

Ответ принят как подходящий

Вы не просите решения, вы спрашиваете причины, по которым ваши два обходных пути устраняют ошибку, хороший способ обучения. Мое почтение.

Чтобы понять, почему вы получаете ошибку и почему вы можете избавиться от нее двумя способами, которые вы нашли сами, вам следует «поиграть в препроцессор» и сделать включение самостоятельно, наблюдая при этом за действием ваших ifdef-ов, они же охранники повторного включения.

Глядя на то, как компилируется B.cpp, вставьте заголовочные файлы туда, где вы делаете включения.

Вы получаете:


#ifndef B_H
#define B_H

#ifndef A_H
#define A_H

#include "B.h" /* not expanded, will be removed next step */

class A {
    friend void B::fun(A&);
};

#endif

class A;

class B {
    public:
        void fun(A&);
};
#endif


void B::fun(A& a) {
    
}

Затем удалите те части, которым мешают #ifdefs, то есть во второй раз заголовка B вы получите:

#ifndef B_H
#define B_H

#ifndef A_H
#define A_H

class A {
    friend void B::fun(A&); /* obviously unknown B */
};

#endif

class A;

class B {
    public:
        void fun(A&);
};
#endif


void B::fun(A& a) {
    
}

Теперь вы понимаете, почему вы получаете ошибку в B.cpp.
Теперь, почему метод 1 работает?

Давайте сделаем то же самое снова, применив это изменение:


#ifndef B_H
#define B_H

/* #include "A.h" */ /* method 1 */

class A;

class B {
    public:
        void fun(A&); /* fine, because forward declaration three lines above is sufficient */
};
#endif
void B::fun(A& a) {
    
}

Теперь, почему метод 2 помогает?


/* method 2, include A.h before B.h into B.cpp */
#ifndef A_H
#define A_H

#ifndef B_H
#define B_H

/* #include "A.h" not expanded because of ifdef */

class A;

class B {
    public:
        void fun(A&); /* fine, because of forward declaration */
};
#endif

class A {
    friend void B::fun(A&); /* fine of forward declaration B having been seen */
};

#endif

/* #include "B.h" not expanded, because of ifdef */

void B::fun(A& a) {
    
}

Короче говоря, оба обходных пути работают, делая предварительное объявление видимым перед использованием A и полностью объявляя B перед использованием.

Внезапно я подумал о другом вопросе. main.cpp -> main.obj и B.cpp -> B.obj, и оба они имеют класс B. Почему при их связывании нет ошибки: множественное определение B? Я буду признателен, если вы расскажете мне об этом.

seineo 26.12.2020 10:13

Интересный вопрос. Пожалуйста, используйте кнопку "Задать вопрос", чтобы создать его.

Yunnosch 26.12.2020 10:13

Чтобы понять, откуда берется ошибка, просто следуйте «цепочке включений», которые происходят при компиляции B.cpp:

  1. B.cpp включает B.h.
  2. B.h включает A.h.
  3. A.h пытается включить B.h, но не делает этого, потому что вы правильно настроили защиту включения.
  4. Теперь компилятор доходит до строки friend void B::fun(A&); в A.h, но на данном этапе B не объявлен!

Другие вопросы по теме