Should operator<< be implemented as a friend or as a member function?
Asked Answered
C

9

171

That's basically the question, is there a "right" way to implement operator<< ? Reading this I can see that something like:

friend bool operator<<(obj const& lhs, obj const& rhs);

is preferred to something like

ostream& operator<<(obj const& rhs);

But I can't quite see why should I use one or the other.

My personal case is:

friend ostream & operator<<(ostream &os, const Paragraph& p) {
    return os << p.to_str();
}

But I could probably do:

ostream & operator<<(ostream &os) {
    return os << paragraph;
}

What rationale should I base this decision on?

Note:

 Paragraph::to_str = (return paragraph) 

where paragraph's a string.

Conditioned answered 25/10, 2008 at 18:19 Comment(2)
BTW you should probably add const to the member functions' signaturesIronmaster
Why return bool from operator<< ? Are you using it as a stream operator or as an overload of the bitwise shift?Metempsychosis
M
152

The problem here is in your interpretation of the article you link.

Equality

This article is about somebody that is having problems correctly defining the bool relationship operators.

The operator:

  • Equality == and !=
  • Relationship < > <= >=

These operators should return a bool as they are comparing two objects of the same type. It is usually easiest to define these operators as part of the class. This is because a class is automatically a friend of itself so objects of type Paragraph can examine each other (even each others private members).

There is an argument for making these free standing functions as this lets auto conversion convert both sides if they are not the same type, while member functions only allow the rhs to be auto converted. I find this a paper man argument as you don't really want auto conversion happening in the first place (usually). But if this is something you want (I don't recommend it) then making the comparators free standing can be advantageous.

Streaming

The stream operators:

  • operator << output
  • operator >> input

When you use these as stream operators (rather than binary shift) the first parameter is a stream. Since you do not have access to the stream object (its not yours to modify) these can not be member operators they have to be external to the class. Thus they must either be friends of the class or have access to a public method that will do the streaming for you.

It is also traditional for these objects to return a reference to a stream object so you can chain stream operations together.

#include <iostream>

class Paragraph
{
    public:
        explicit Paragraph(std::string const& init)
            :m_para(init)
        {}

        std::string const&  to_str() const
        {
            return m_para;
        }

        bool operator==(Paragraph const& rhs) const
        {
            return m_para == rhs.m_para;
        }
        bool operator!=(Paragraph const& rhs) const
        {
            // Define != operator in terms of the == operator
            return !(this->operator==(rhs));
        }
        bool operator<(Paragraph const& rhs) const
        {
            return  m_para < rhs.m_para;
        }
    private:
        friend std::ostream & operator<<(std::ostream &os, const Paragraph& p);
        std::string     m_para;
};

std::ostream & operator<<(std::ostream &os, const Paragraph& p)
{
    return os << p.to_str();
}


int main()
{
    Paragraph   p("Plop");
    Paragraph   q(p);

    std::cout << p << std::endl << (p == q) << std::endl;
}
Metempsychosis answered 25/10, 2008 at 21:53 Comment(11)
Why is the operator<< private:?Leodora
@MattClarkson: Its not. Its a friend function declaration thus not part of the class and thus not affected by the access specifiers. I generally put the friend function declarations next to the data that they accesses.Metempsychosis
Good link. Makes this answer even better!Leodora
Why does it need to be a friendly function, if you are using public function to access data? Sorry, if the question is stupid.Nutlet
@SemyonDanilov: Why would you break encapsulation and add getters! freiend is a way to extended the public interface without breaking encapsulation. Read programmers.stackexchange.com/a/99595/12917Metempsychosis
@LokiAstari But surely that's an argument for either removing to_str or making it private. As it stands, the streaming operator does not have to be a friend, as it uses only public functions.Burdensome
@deworde: Sure. I have no issue with that. I (personally) would still make the operator<< a friend; not for access privileges but to document that it is part of the public API. But I would not be cry in a code review if it was not a friend. That comes down to personal taste.Metempsychosis
@LokiAstari And thus, the "unnecessary friend heresy" is allowed to propogate? I SAY THEE NAY SIR! To the battlefield! #YAllKnowItsTimeForAReligiousWarBurdensome
@deworde: A the old 30/30/40 rule. 30% vigorously advocate one method; 30% vigorously advocate the exact opposite and 40% role their eyes and say let us know who wins.Metempsychosis
@Burdensome If you make it non-friend, and need to change it to access private members, you need to change the header, which is often ungood. For something that can be reasonably deemed a friend, whether or not it's currently taking advantage of that friendship, I think it's worthwhile to just stick friend on it upfront.Prynne
@MartinYork could you please take a look and update the link that you have mentioned at the end of the first line? It seems broken! Thank you!Megan
C
60

You can not do it as a member function, because the implicit this parameter is the left hand side of the <<-operator. (Hence, you would need to add it as a member function to the ostream-class. Not good :)

Could you do it as a free function without friending it? That's what I prefer, because it makes it clear that this is an integration with ostream, and not a core functionality of your class.

Cirrate answered 25/10, 2008 at 18:24 Comment(4)
"not a core functionality of your class." That's what "friend" means. If it were core functionality, it would be in the class, not a friend.Seringapatam
@Seringapatam I think my first sentence explains why it would be impossible in this case to add the function as a member function. A friend function has the same rights as a member function (this is what friend means), so as a user of the class, I would have to wonder why it would need that. This is the distinction I am trying to make with the wording "core functionality".Cirrate
Could you give an example of you do it as a free function?Thibodeaux
@user It means to place the function outside the class... e.g., std::ostream& operator<<(std::ostream& os, const Foo& foo); inside your header, and then implementing in your source.Resume
B
33

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.

Benedicite answered 25/10, 2008 at 22:22 Comment(2)
Doesn't your generic basic_istream and basic_ostream templated code already cover the std::ostream- and std::istream-specific versions since the latter two are just instantiations of the former using chars?Smokejumper
@Rhys Ulerich: Of course. I use only the generic, templated version, if only because on Windows, you have to deal with both char and wchar_t code. The secondth version's only merit is to appear as more simple than the first. I'll clarify my post about that.Benedicite
P
11

It should be implemented as a free, non-friend functions, especially if, like most things these days, the output is mainly used for diagnostics and logging. Add const accessors for all the things that need to go into the output, and then have the outputter just call those and do formatting.

I've actually taken to collecting all of these ostream output free functions in an "ostreamhelpers" header and implementation file, it keeps that secondary functionality far away from the real purpose of the classes.

Prelusive answered 25/10, 2008 at 18:59 Comment(0)
I
9

The signature:

bool operator<<(const obj&, const obj&);

Seems rather suspect, this does not fit the stream convention nor the bitwise convention so it looks like a case of operator overloading abuse, operator < should return bool but operator << should probably return something else.

If you meant so say:

ostream& operator<<(ostream&, const obj&); 

Then since you can't add functions to ostream by necessity the function must be a free function, whether it a friend or not depends on what it has to access (if it doesn't need to access private or protected members there's no need to make it friend).

Ironmaster answered 25/10, 2008 at 18:29 Comment(1)
It's worth mentioning access to modify ostream would be required when using the ostream.operator<<(obj&) ordering; hence the free function. Otherwise the user type need be a steam type to accommodate access.Toscano
S
5

Just for completion sake, I would like to add that you indeed can create an operator ostream& operator << (ostream& os) inside a class and it can work. From what I know it's not a good idea to use it, because it's very convoluted and unintuitive.

Let's assume we have this code:

#include <iostream>
#include <string>

using namespace std;

struct Widget
{
    string name;

    Widget(string _name) : name(_name) {}

    ostream& operator << (ostream& os)
    {
        return os << name;
    }
};

int main()
{
    Widget w1("w1");
    Widget w2("w2");

    // These two won't work
    {
        // Error: operand types are std::ostream << std::ostream
        // cout << w1.operator<<(cout) << '\n';

        // Error: operand types are std::ostream << Widget
        // cout << w1 << '\n';
    }

    // However these two work
    {
        w1 << cout << '\n';

        // Call to w1.operator<<(cout) returns a reference to ostream&
        w2 << w1.operator<<(cout) << '\n';
    }

    return 0;
}

So to sum it up - you can do it, but you most probably shouldn't :)

Setaceous answered 2/6, 2017 at 9:42 Comment(0)
O
1

I would recommend defining operator<< as a private friend aka hidden friend so that it is only found via the argument-dependent lookup:

class Paragraph {
 // ...
 private:
  friend std::ostream& operator<<(std::ostream& os, const Paragraph& p) {
    return os << p.to_str();
  }
};

Hidden friends have a number of benefits, most importantly it won't pollute the overload set which can speed up compilation and improve diagnostics. It also prevents undesirable implicit conversions.

Observant answered 28/12, 2023 at 0:35 Comment(0)
L
0

operator<< implemented as a friend function:

#include <iostream>
#include <string>
using namespace std;

class Samp
{
public:
    int ID;
    string strName; 
    friend std::ostream& operator<<(std::ostream &os, const Samp& obj);
};
 std::ostream& operator<<(std::ostream &os, const Samp& obj)
    {
        os << obj.ID<< “ ” << obj.strName;
        return os;
    }

int main()
{
   Samp obj, obj1;
    obj.ID = 100;
    obj.strName = "Hello";
    obj1=obj;
    cout << obj <<endl<< obj1;

} 

OUTPUT:
100 Hello
100 Hello

This can be a friend function only because the object is on the right hand side of operator<< and argument cout is on the left hand side. So this can't be a member function to the class, it can only be a friend function.

Lizzielizzy answered 20/1, 2012 at 11:15 Comment(2)
i dont think there is a way to write this as a member funtion!!Lizzielizzy
Why is everything bold. Let me remove this.Meemeece
A
0

friend operator = equal rights as class

friend std::ostream& operator<<(std::ostream& os, const Object& object) {
    os << object._atribute1 << " " << object._atribute2 << " " << atribute._atribute3 << std::endl;
    return os;
}
Abri answered 1/2, 2016 at 12:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.