Thread: Overloading the << operator.

  1. #1
    Registered User
    Join Date
    Sep 2004
    Posts
    80

    Overloading the << operator.

    I understand overloading for other operators such as +, == and so on...
    but I find this code a little confusing.
    Code:
    friend ostream &operator <<(ostream &o, Text &t)
    {
    	o << t.txtString;
    	return o;
    }
    I get it to work without any problem but I don't really understand the thing
    about the references to ostream and why the function is supposed to
    return a that ostream object. Could anyone please try to give me a detailed
    explanation. I have tried google and found sites about this but those sites
    didn't have any really good explanations of what's really going on with that code.

  2. #2
    Registered User
    Join Date
    Aug 2003
    Posts
    127
    i think it should look like this
    Code:
    friend ostream &operator <<(ostream &o, const Text &t)
    {
    	o << t.txtString;
    	return o;
    }
    returning an ostream ref is used for cout<<aaa<<bbb<<ccc;
    if not, you just can do it like this
    cout<<aaa;
    cout<<bbb;
    cout<<ccc;
    Nana C++ Library is a GUI framework that designed to be C++ style, cross-platform and easy-to-use.

  3. #3
    Registered User
    Join Date
    Sep 2004
    Posts
    80
    Thanks for your reply.
    ok now I know the reason a reference should be used but I still don't understand why it
    works that way. What do I have to know to fully understand this? How the ostream class
    looks?

  4. #4
    Registered User hk_mp5kpdw's Avatar
    Join Date
    Jan 2002
    Location
    Northern Virginia/Washington DC Metropolitan Area
    Posts
    3,817
    Jinhao's example is demonstrating chaining:

    Code:
    cout << aaa << bbb << ccc;
    Returning a reference is important when chaining although not as much for output streams as it is for input streams when bad input can easily set the stream into a failed state. It is much more rare for an output stream to go into a failed state. Let's say you have this:

    Code:
    Text t1, t2, t3;
    ...
    cout << t1 << t2;
    cout << t3;
    In the above code, the output of the first t1 object should return a reference to the modified cout stream which is then used to output t2 by a second call to the overloaded operator<<. If you had been returning copies instead of references, then the operator<< function for t2 would be working with a copy of the cout stream object instead of the actual cout stream object. If the output of the t2 object caused the stream to go into a failed state for some reason, the failure would only affect the copy of cout instead of the original cout object stream used in the beginning. By the time you get to output t3 you wouldn't know that cout is supposed to be in a failed state because the failed state only affected the copy of cout used to output t2 and not the cout we are trying to use to output t3. Don't know if I can explain it any better than that.
    "Owners of dogs will have noticed that, if you provide them with food and water and shelter and affection, they will think you are god. Whereas owners of cats are compelled to realize that, if you provide them with food and water and shelter and affection, they draw the conclusion that they are gods."
    -Christopher Hitchens

  5. #5
    Registered User
    Join Date
    Apr 2003
    Posts
    2,663
    Thanks for your reply.
    ok now I know the reason a reference should be used but I still don't understand why it
    works that way. What do I have to know to fully understand this? How the ostream class
    looks?
    It's not clear what you are confused about:

    1) Do you not understand what a reference is? It's essentially a constant pointer that is implemented with a different syntax:

    int a = 10;
    int& myRef = a;
    cout<<myRef<<endl;

    2) Do you not understand what a stream object is?

    cout is a stream object, and you use it like this:

    cout<<10<<endl;

    With that statement you are using the cout object and it's member function operator<<() to display output to the console window.

    cin is another stream object, and you use it like this:

    cin>>myVar;

    In that statement, cin is a stream object and you are using it's member function operator>>() to insert data into myVar.

    When you are doing file input and output, you create your own stream objects, and you use them to insert data into a file or extract data from a file.

    3) Do you not understand about chaining operators together, e.g.:

    a=b=c;

    and why the return type would determine whether you could do that or not? Suppose you wanted to define equals for a class so you would be able to write statements like this:

    a=b;

    You might decide, "I don't need to return anything, all I need to do is copy what's on the right into what's on the left." That would work just fine. But, what if you want to be able to write statements like this:

    a=b=c;

    That is equivalent to:

    a=(b=c);

    So, C++ evaluates what's in the parentheses first, and executes b=c. If you don't return anything, then you end up with:

    a=nothing;

    However, it's most likely that you want 'a' to be the same as b. So, the basic rule for operator=() is: you want to return a reference to what's on the left hand side of the operator. If you return a reference to the left hand side, the second part of the statement will yield:

    a = b&;

    To C++, that looks identical to the part between the parentheses. So, C++ calls your operator=() function again, which copies b into a in exactly the same manner. Therefore, you end up with c copied to b, and b copied to a.

    4) Or, do you not understand the format of the overloaded operator<< code you posted?
    I understand overloading for other operators such as +, == and so on...
    but I find this code a little confusing.
    That could be because you're used to writing an overloaded operator function as a member function. I'll try to explain what's going on in terms of the operator<() function--instead of the operator<<() and stream objects--in the hope that it will be easier to understand.

    When the operator<() function is a member function, then it can be used like this:

    a < 40

    In that case, the object of the class is on the left, and you are calling the operator<() function on the object a. That allows you to implement the operator<() function as a member function, and the format of the member function is:

    bool operator<(int num);

    That's a format you're probably familiar with. Curiously, only one parameter is needed for the binary operator<, namely the right hand side--even though there are two operands. That's because the this pointer, which represents the left hand side, is automatically passed to a member function, so you actually do have two parameters which correspond to the two operands.

    But, what if you want to be able to write statements like this:

    40 < a;

    You can't implement that as a member function because the left hand side is not a member of your class. Instead, you are calling the operator<() on the integer 40. So, you have to implement the operator as a friend function(if you need access to private members of 'a') or as a regular function(using public get() functions to gain access to the members of 'a'). The consequence of that is the format of the function signature changes--there is no this pointer for non-member functions. That means the left hand side is not automatically passed to the function as it was for a member function. As a result, you have to explicitly declare both parameters in the function signature:

    ReturnType& operator<(int num, ClassType a);

    The order of the parameters inside the parentheses matches the left side-right side order of the statement you are trying to accommodate, e.g.:

    Code:
        40   <     a
    (int num, ClassType a)
    Applying all that to your stream object and the operator<<, you should be able to see that if you want to write a statement of the form:

    outFile<<MyClassObj

    that parallels the order of the operands in this statement:

    40 < a;

    You have something on the left that is not a member of your class (outFile), and you have your class member on the right (MyClassObj). So, you can't make your class's operator<<() function a member function. That means it has to be a friend function(if you need access to the private members of a) or it has to be a regular function. In turn, that means the function signature will have to list both operands in the statement.

    Looking at the statement you are trying to overload again:

    outFile<<MyClassObj

    it can be seen that the first operand is of type ostream and the other is of type MyClassObj. So, you can start out writing the function signature like this:

    something operator<<(ostream& outFile, MyClass& MyClassObj)

    The last thing to determine is what the return type should be. Once again, you typically look to see what return type would be required in order to be able to write chained statements like:

    outFile<<10<<myClassObj;

    The operator<< starts evaluation on the left, so the statement is equivalent to:

    (outFile<<10) << myClassObj;

    Hopefully, you can see that you want the expression in parentheses to return a stream object, so after it is evaluated you end up with:

    outFile << myClassObj;

    That requires that you return what's on the left of the operator:

    (outFile<<10)

    which is an ostream object, and to avoid unnecessary copying of the object, you use a reference type. Therefore, you end up with the function signature:

    ostream& operator<<(ostream& outFile, MyClass& myClassObj);

    (Even if you don't want to be able to chain operators together, there is another reason why you may want to return a stream object. A stream object can be used as an entire if or while conditional: if the stream sets any error flags, the object will evaluate to false, otherwise the object will evaluate to true. That allows you to do things like:
    Code:
    while( outFile<<myArray[i] ) //Returns a stream object which evaluates
    {                            //to true if no error flags are set and 
                                 //the stream is open for business.
                                 //When you hit eof the eof error flag will be set
                                 //and the loop will terminate
    }
    As a final touch, you usually make your function parameters constant when you want to ensure your function doesn't change the parameters. If you make them constant and you accidentally try to change them in your function, the compiler will alert you with an error. Note that adding const in front of a function parameter does not in anyway change the type of the variable you send to the function--it only determines what your function can do with that variable. One other twist: const objects can only call const member functions. So, if you declare a class member parameter as const, and then inside your operator function you try to call a member function with the object, you will get an error if the member function is not declared const as well.
    Last edited by 7stud; 05-31-2005 at 02:45 AM.

  6. #6
    Registered User
    Join Date
    Sep 2004
    Posts
    80
    Thank you both for your excellent explanations. I think I get the idea now. Thanks again!

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. unary operator overloading and classes
    By coletek in forum C++ Programming
    Replies: 9
    Last Post: 01-10-2009, 02:14 AM
  2. Smart pointer class
    By Elysia in forum C++ Programming
    Replies: 63
    Last Post: 11-03-2007, 07:05 AM
  3. overloading the output operator <<
    By neandrake in forum C++ Programming
    Replies: 9
    Last Post: 12-07-2004, 02:25 PM
  4. overloading operator <<
    By noob2c in forum C++ Programming
    Replies: 4
    Last Post: 09-23-2003, 10:11 AM
  5. operator overloading and dynamic memory program
    By jlmac2001 in forum C++ Programming
    Replies: 3
    Last Post: 04-06-2003, 11:51 PM