Функция класса Friend или Friend - Вперед декларация и включение заголовка

Да, тема вопроса обсуждалась так много раз. И я почти понимаю эту разницу. У меня есть только одно сомнение, связанное с примером в книге.

Этот вопрос связан с моим предыдущим вопросом, где я представил 2 класса, взятых в качестве примера в книге С++ Primer.

В отношении этих классов в книге цитируется следующий абзац, специально связанный с объявлением функции-члена класса WindowManager в качестве функции-друга. Вот что он говорит:

Создание функции-члена для друга требует тщательной структуризации наших программ для учитывать взаимозависимости между декларациями и определениями. В этом Например, мы должны заказать нашу программу следующим образом:

  • Сначала определите класс Window_mgr, который объявляет, но не может определить, очистить. Экран должен быть объявлен до того, как clear сможет использовать элементы экрана.
  • Далее, определите класс Screen, включая декларацию друга для очистки.
  • Наконец, определите clear, который теперь может ссылаться на элементы в Screen.

Код, представленный мной в этом вопросе, следует только этой структуре. Но, похоже, он не работает. Это заставляет меня думать, если указанные выше пункты ошибочны, или я не реализовал его правильно.

Проблема заключается в том, что когда я объявляю функцию clear функцией friend в классе ScreenCls, я попадаю в циклическое включение файлов заголовков. Я еще раз расскажу о конкретной части обоих классов:

ScreenCls.h:

#ifndef SCREENCLS_H
#define SCREENCLS_H

#include <iostream>

#include "WindowManager.h"

using namespace std;

class ScreenCls {
    friend void WindowManager::clear(ScreenIndex);

    // Some other code
}

Здесь я должен включить заголовочный файл WindowManager.h, так как функция clear теперь использует ScreenIndex, определенную там. Форвардная декларация не будет работать здесь (исправьте меня, если я ошибаюсь).

Теперь, перейдем к WindowManager.h:

#ifndef WINDOWMANAGER_H
#define WINDOWMANAGER_H

#include <iostream>
#include <vector>
#include "ScreenCls.h"

using namespace std;

class WindowManager {
public:
    // location ID for each screen on window
    using ScreenIndex = vector<ScreenCls>::size_type;

private:
    vector<ScreenCls> screens{ ScreenCls(24, 80, ' ') };
};

И сконцентрируйтесь на объявлении screens здесь. Они использовали инициализатор списка для добавления значения по умолчанию ScreenCls в vector. Итак, здесь снова нужно включить WindowManager.h. И теперь мы находимся в циклическом включении. Это предотвратит создание моего проекта.

Однако, если я изменяю объявление функции друга, чтобы сделать весь класс как друга, тогда я могу работать с forward declaring классом WindowManager. В этом случае он будет работать нормально.

Итак, в основном функция друзей здесь не работает, но класс друзей работает. Итак, так ли, что вышеупомянутые пункты не идут хорошо с их реализацией, или что-то не так с моими классами? Я просто хочу знать это, чтобы четко понять концепцию header inclusion и forward declaration.

Вопросы, связанные с моим предыдущим вопросом, описывают это хорошо. Но это просто, что он не работает в вышеуказанной ситуации, поэтому я снова прошу его.

Ответ 1

Я думаю, ваша проблема связана с инициализатором экрана. Вы не можете инициализировать любые данные в *.h файлах, находящихся внутри класса. Итак, я предлагаю вам сделать что-то вроде этого:

#ifndef WINDOWMANAGER_H
#define WINDOWMANAGER_H

#include <iostream>
#include <vector>
//#include "ScreenCls.h"

using namespace std;
class ScreenCls;

class WindowManager {
public:
    // location ID for each screen on window
    using ScreenIndex = vector<ScreenCls>::size_type;

private:
    vector<ScreenCls> screens; //{ ScreenCls(24, 80, ' ') };    remove this
};

Ответ 2

Пока вы не используете этот класс, то есть вызываете метод объекта или вызываете новый экземпляр или резервируете массив экземпляров класса, который вы можете использовать только с объявлениями вперед. Как правило, tumb: если компилятор не жалуется при использовании форвардных объявлений, используйте их и избегайте включения, которые замедляют компиляцию.

Единственная опасность: когда вы используете множественное наследование и не включаете include, приведение не будет работать хорошо - но это обычно вы делаете в .cpp, где вы должны включать классы, которые вы используете.