Option 1: Not inline
You can of course move the definition of B::geta
to a B.cpp file which includes A.hpp and remove the inline
keyword. But this might make compiler optimizations less likely.
Option 2: Weird #include
logic
Your definitions as they are can only compile if the compiler sees a forward declaration of A
, the definition of B
, the definition of A
, and the definition of B::geta
, all in that order. So if you want the definition of A
in one file and the definition of B
in another, you'll need to get the preprocessor to do some switching back and forth between files.
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
So now if a file does #include "B.hpp"
, the preprocessor will:
- Output the
class A;
and definition of B
from the first part of B.hpp.
- Define
INCLUDED_FROM_B_HPP
.
- Output the definitions in A.hpp.
- Clear the definition of
INCLUDED_FROM_B_HPP
.
- Output the definitions of inline members of
B
from the second part of B.hpp.
If a file does #include "A.hpp"
first, things are a bit trickier:
- Since
INCLUDED_FROM_B_HPP
is not set, just immediately go into B.hpp.
- Output the
class A;
and definition of B
from the first part of B.hpp.
- Define
INCLUDED_FROM_B_HPP
.
- When the
#include "A.hpp"
in the middle of B.hpp is encountered, the preprocessor will go recursively back into A.hpp. But this time since INCLUDED_FROM_B_HPP
is defined, it outputs the code contents of A.hpp.
- Clear the definition of
INCLUDED_FROM_B_HPP
.
- Output the definitions of inline members of
B
from the second part of B.hpp.
Option 3: friend class B;
Instead of specifying just the one member function B::geta
to friend, just befriend the class itself, with the declaration friend class B;
. Now A.hpp doesn't need to include B.hpp, so there's no circular dependency issue.
This doesn't much decrease encapsulation from the point of view of what access is and isn't possible, since normally any programmer who can modify any part of class B
can also modify B::geta
. But it does open possibilities of "accidentally" using non-public members of A
in other members of B
.
Option 4: Refactor access method
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;
}
This code introduces a new sort of encapsulation: now class B
can only get the value from member a
, can't modify that value, and can't access any other non-public members of A
. Additional accessors could be added for other members as appropriate. To permit modifying a member, the class could either provide "set" accessors, or an accessor that returns a reference. For non-public functions, the class could provide wrapper functions that just pass through to the actual function.
It's still true that it's possible for members of B
other than B::geta
to exploit the friendship, but now typing out A::AccessForB::
can't really be considered an accident.