// stroustrup way
friend bool operator== (MyClass &lhs, MyClass &rhs);
Arguments should be const:
friend bool operator==(const MyClass& lhs, const MyClass& rhs);
This is preferred as it works when the first argument can be implicitly constructed.  For example, if std::string only had a member function operator==, then "abc" == my_std_string would not invoke it!  But, the non-member function can be invoked by implicitly constructing a string from "abc" (better yet in this particular case, a separate bool operator==(const char*, const std::string&) can be provided for performance reasons, but the point still stands - non-member functions can help ensure the operator works with the user-defined-type on either side).
Separately, implicit constructors are a bit dangerous - and you want to think hard about the convenience versus danger of using them.
Similarly, if you had ObjectOfAnotherClassType == ObjectOfMyClassType, and AnotherClass sported a casting operator such as operator MyClass() const, then the non-member/friend operator== would allow the cast to kick in and be able to do the comparison; the member operator== form would not.
A final point: you only need to make the non-member operator== a friend if there's no other way to access the data you need to compare.  Otherwise,  you can declare/define it outside the class, optionally inline if you want the implementation in a header that may be included from multiple translation units eventually linked into the same executable.  Not much harm though, and making it a friend is the only way to put the definition inside a class template, where you don't have to repeat the "template <typename ...>" stuff and parameters....