Let's look at this from a purely conceptual level for a minute. This what your design says:
- There exists one conceptual Foo entity for every Bar instance (because I can get a Foo from a Bar, and its state depends on which Bar I get it from).
- Each Foo instance belongs to the Bar instance it came from (because operations on a Foo change the Bar it came from - the next time I ask for a Foo from a Bar, the previous Foo's changes are reflected).
- A Foo has the same lifetime as its Bar (because I can ask for it at any time in the Bar's lifetime, I can use it as long as Bar exists, and the caller of get_foo() does not manage the lifetime of the returned Foo object).
Another way of looking at it is that Foo is already designed as part of Bar's internal state, a "conceptual member variable", regardless of whether it is actually implemented that way.
Given what your public interface is already telling you, how does returning a non-const reference to a private member really break encapsulation? Could you change the implementation so that Foo isn't a private member variable, yet still use the same public interface? Yes, you could. The only implementation changes that would force you to change the public interface ALSO force you to change the conceptual interface described above.
Implementation rules of thumb can be over-applied. Move past mechanics and look at conceptual design instead. Assuming you're OK with what your design is implying, in this case I say that returning a reference to a private member variable does NOT break encapsulation. At least that's my take on it.
An alternative is to have Foo and Bar less tightly coupled.
class Bar {
public:
Foo get_foo()
{
return foo;
}
set_foo(Foo new_foo)
{
// Update foo with new_foo's values
foo = new_foo;
}
private:
Foo foo;
};
In this case, Foo reflects some part of Bar's internal state at the time it was requested, but isn't tied to the Bar it came from. You have to explicitly call set_foo()
to update Bar. Without that requirement, Foo really is conceptually a member variable regardless of how you implement it.