If possible, as non-member and non-friend functions.
As described by Herb Sutter and Scott Meyers, prefer non-friend non-member functions to member functions, to help increase encapsulation.
In some cases, like C++ streams, you won't have the choice and must use non-member functions.
But still, it does not mean you have to make these functions friends of your classes: These functions can still acess your class through your class accessors. If you succeed in writting those functions this way, then you won.
About operator << and >> prototypes
I believe the examples you gave in your question are wrong. For example;
ostream & operator<<(ostream &os) {
return os << paragraph;
}
I can't even start to think how this method could work in a stream.
Here are the two ways to implement the << and >> operators.
Let's say you want to use a stream-like object of type T.
And that you want to extract/insert from/into T the relevant data of your object of type Paragraph.
Generic operator << and >> function prototypes
The first being as functions:
// T << Paragraph
T & operator << (T & p_oOutputStream, const Paragraph & p_oParagraph)
{
// do the insertion of p_oParagraph
return p_oOutputStream ;
}
// T >> Paragraph
T & operator >> (T & p_oInputStream, const Paragraph & p_oParagraph)
{
// do the extraction of p_oParagraph
return p_oInputStream ;
}
Generic operator << and >> method prototypes
The second being as methods:
// T << Paragraph
T & T::operator << (const Paragraph & p_oParagraph)
{
// do the insertion of p_oParagraph
return *this ;
}
// T >> Paragraph
T & T::operator >> (const Paragraph & p_oParagraph)
{
// do the extraction of p_oParagraph
return *this ;
}
Note that to use this notation, you must extend T's class declaration. For STL objects, this is not possible (you are not supposed to modify them...).
And what if T is a C++ stream?
Here are the prototypes of the same << and >> operators for C++ streams.
For generic basic_istream and basic_ostream
Note that is case of streams, as you can't modify the C++ stream, you must implement the functions. Which means something like:
// OUTPUT << Paragraph
template <typename charT, typename traits>
std::basic_ostream<charT,traits> & operator << (std::basic_ostream<charT,traits> & p_oOutputStream, const Paragraph & p_oParagraph)
{
// do the insertion of p_oParagraph
return p_oOutputStream ;
}
// INPUT >> Paragraph
template <typename charT, typename traits>
std::basic_istream<charT,traits> & operator >> (std::basic_istream<charT,traits> & p_oInputStream, const CMyObject & p_oParagraph)
{
// do the extract of p_oParagraph
return p_oInputStream ;
}
For char istream and ostream
The following code will work only for char-based streams.
// OUTPUT << A
std::ostream & operator << (std::ostream & p_oOutputStream, const Paragraph & p_oParagraph)
{
// do the insertion of p_oParagraph
return p_oOutputStream ;
}
// INPUT >> A
std::istream & operator >> (std::istream & p_oInputStream, const Paragraph & p_oParagraph)
{
// do the extract of p_oParagraph
return p_oInputStream ;
}
Rhys Ulerich commented about the fact the char-based code is but a "specialization" of the generic code above it. Of course, Rhys is right: I don't recommend the use of the char-based example. It is only given here because it's simpler to read. As it is only viable if you only work with char-based streams, you should avoid it on platforms where wchar_t code is common (i.e. on Windows).
Hope this will help.