Вариант 1: не встроен
Конечно, вы можете переместить определение B::geta
в файл B.cpp, который включает в себя A.hpp, и удалить ключевое слово inline
. Но это может сделать оптимизацию компилятора менее вероятной.
Вариант 2: Странная #include
логика
Ваши определения в том виде, в каком они есть, могут быть скомпилированы, только если компилятор увидит предварительное объявление A
, определение B
, определение A
и определение B::geta
, все в этом порядке. Поэтому, если вам нужно определение A
в одном файле и определение B
в другом, вам нужно заставить препроцессор переключаться между файлами.
A.hpp
// NO A_HPP include guard!
#ifdef INCLUDED_FROM_B_HPP
class A
{
protected:
int a;
public:
A();
~A(){};
friend void B::geta(A& ao);
};
inline A::A()
{
a = 2;
cout << a;
}
#else
# include "B.hpp"
#endif
B.hpp
#ifndef B_HPP
#define B_HPP
class A;
class B
{
protected:
int b;
public:
B();
~B(){};
void geta(A& ao);
};
#define INCLUDED_FROM_B_HPP
#include "A.hpp"
#undef INCLUDED_FROM_B_HPP
inline B::B()
{
b = 1;
cout << b;
}
inline void B::geta(A& ao)
{
b = ao.a;
cout << b;
}
#endif
Так что теперь, если файл делает #include "B.hpp"
, препроцессор будет:
- Выведите
class A;
и определение B
из первой части B.hpp.
- Определить
INCLUDED_FROM_B_HPP
.
- Вывести определения в A.hpp.
- Очистить определение
INCLUDED_FROM_B_HPP
.
- Вывести определения встроенных членов
B
из второй части B.hpp.
Если файл сначала #include "A.hpp"
, все немного сложнее:
- Так как
INCLUDED_FROM_B_HPP
не установлен, просто немедленно перейдите в B.hpp.
- Выведите
class A;
и определение B
из первой части B.hpp.
- Определить
INCLUDED_FROM_B_HPP
.
- Когда встречается
#include "A.hpp"
в середине B.hpp, препроцессор рекурсивно возвращается в A.hpp. Но на этот раз, так как INCLUDED_FROM_B_HPP
определен, он выводит содержимое кода A.hpp.
- Очистить определение
INCLUDED_FROM_B_HPP
.
- Вывести определения встроенных членов
B
из второй части B.hpp.
Вариант 3: friend class B;
Вместо указания только одной функции-члена B::geta
другу, просто подружитесь с самим классом объявлением friend class B;
. Теперь A.hpp не нужно включать B.hpp, поэтому проблема круговой зависимости отсутствует.
Это не сильно уменьшает инкапсуляцию с точки зрения того, что такое доступ и невозможен, поскольку обычно любой программист, который может изменить любую часть class B
, может также модифицировать B::geta
. Но это открывает возможности «случайного» использования непубличных членов A
в других членах B
.
Вариант 4: метод доступа к Refactor
A.hpp
#ifndef A_HPP
#define A_HPP
class B;
class A
{
protected:
int a;
public:
A();
~A(){};
class AccessForB {
private:
static int geta(A& aobj) { return aobj.a; }
friend class ::B;
};
};
inline A::A()
{
a = 2;
cout << a;
}
#endif
B.hpp
...
inline void B::geta(A& ao)
{
b = A::AccessForB::geta(ao);
cout << b;
}
Этот код представляет новый вид инкапсуляции: теперь class B
может получать значение только от члена a
, не может изменять это значение и не может получить доступ к любым другим непубличным элементам A
. Дополнительные средства доступа могут быть добавлены для других участников по мере необходимости. Чтобы разрешить изменение члена, класс может предоставить «набор» методов доступа или метод доступа, который возвращает ссылку. Для непубличных функций класс может предоставлять функции-оболочки, которые просто передаются действительной функции.
Все еще верно, что члены B
, кроме B::geta
, могут использовать дружбу, но теперь ввод A::AccessForB::
не может считаться случайностью.