Longer discussion of this here: Why do objects of the same class have access to each other's private data?
Simple example using Line objects with an integer length. The operator+
overload function has access to the other line's private length (the parameter const Line &line
, aka the line being added to this
line). Same thing is true for a non-operator-overload function (printOtherLine
) and a friend function (printFriendLine
). Once again the Line being passed as a parameter is not this
object.
Why is this the case?
#include <iostream>
class Line
{
public:
Line()
{
length = 0;
}
Line(int length)
{
this->length = length;
}
Line operator+(const Line &line)
{
Line newLine(this->length + line.length); // I would have thought
// this would be line.getLength()
// instead of line.length
return newLine;
}
int getLength()
{
return length;
}
void printOtherLine(const Line &otherLine){
std::cout << "Other Line: " << otherLine.length << std::endl;
}
void printLine(int lineNumber){
std::cout << "Line " << lineNumber << ": " << this->length << std::endl;
}
friend void printFriendLine(const Line &friendlyLine);
private:
int length;
};
void printFriendLine(const Line &friendlyLine){
std::cout << "Friendly Line: " << friendlyLine.length << std::endl;
}
// This function will not compile
// void printUnassociatedLine(const Line &line){
// std::cout << "Unassociated Line: " << line.length << std::endl;
// }
int main()
{
Line l1(10);
l1.printLine(1);
Line l2(15);
l2.printLine(2);
Line l3 = l1 + l2;
l3.printLine(3);
Line l4(7);
l3.printOtherLine(l4);
printFriendLine(l4);
return 0;
}
Line 1: 10
Line 2: 15
Line 3: 25
Other Line: 7
Friendly Line: 7
In C++ all of the code of a class X has access to all of X's parts, regardless of which object.
This even includes code in class definitions nested within X.
Regarding rationale that's informed speculation on my part, a post-rationalization, but the purpose of access limitations is to make a class easier to use correctly and more difficult to use incorrectly, for other code. The class' own code is trusted and has to use all of the class anyway. So it doesn't make much sense to restrict the class' own code, in any way.
There is an interesting technical issue regarding access to protected
data members. A protected data member introduced in a class Base
can be directly access in a derived class Derived
, but only for objects whose statically known type is Derived
or classes derived from Derived
. This ensures that you cannot inadvertently gain access to and make your code depend on someone else's class' internals, just by deriving from a common base class.
class Base
{
protected:
int x_ = 666;
};
class Derived
: public Base
{
public:
auto uh_oh( Base const& other ) const
-> int
{ return x_ * other.x_; } //← Nyet!
};
auto main()
-> int
{
Derived a
Derived b;
return a.uh_oh( b );
}
There are two common workarounds where one really does need that access. One is to introduce an accessor function in the base class. And the other is to use a type system loophole for member pointers, like this:
class Base
{
protected:
int x_ = 666;
};
class Derived
: public Base
{
public:
auto uh_oh( Base const& other ) const
-> int
{ return x_ * other.*&Derived::x_; } // A somewhat dirty trick.
};
auto main()
-> int
{
Derived a;
Derived b;
return a.uh_oh( b );
}
There is also an implied issue of best practice here: when you have an accessor like getLength
, and also access to "the" member variable length
, should one or the other be preferably used?
Well, it might be that one later desires to change the implementation, e.g. the length might be computed in some way rather than directly stored, and then it can be less work to do that if the code in general uses the accessor function. But on the other hand it can be more work to write code using the accessor, and it can be more work to debug such code. So at least as far as I can see it's down to common sense in each case, gut-feeling decisions. ;-)