The most important thing to consider when designing classes is, "what is the invariant?" Classes are design to protect invariants. So, classes must be the tiniest as possible to ensure that invariant is properly protected. If you have so many member/friend functions, there is more code to review.
From this point of view, if a class has members which don't need to be protected (for example, a boolean which its corresponding get/set functions can be freely changed by the user), is better to put that attributes as public and remove the get/set functions (more or less, these are the Bjarne Stroustrup words).
So, which functions must be declared inside the class and which ones out? Inside functions must be these minimum required set of function to protect the invariant, and outside functions must be any function that can be implemented using the other ones.
The thing with operator overloading is another history, because the criteria to put some operators inside, and some other outside, is because of syntactical issues related to implicit conversions and so on:
class A
{
private:
int i_i;
public:
A(int i) noexcept : i_i(i) {}
int val() const noexcept { return i_i; }
A operator+(A const& other) const noexcept
{ return A(i_i + other.i_i); }
};
A a(5);
cout << (4 + a).val() << endl;
In this case, since the operator is defined inside the class, the compiler doesn't find the operator, because the first argument is an integer (when an operator is called, the compiler search for free functions and functions declared inside the class of the first argument).
When declared outside:
class A
{
private:
int i_i;
public:
A(int i) noexcept : i_i(i) {}
int val() const noexcept { return i_i; }
};
inline A operator+(A const& first, A const& other) const noexcept;
{ return A(first.val() + other.val()); }
A a(5);
cout << (4 + a).i_i << endl;
In these case, the compiler find the operator, and try to perform an implicit conversion of the first parameter from int to A, using the proper A's constructor.
In these case, the operator can also be implemented using other functions, so, it doesn't need to be friend
and you can be sure the invariant is not compromised with that additional function. So, in these concrete example, moving the operator outside is good for two reasons.