Pergunta

Having to declare "global friend operator overloading" to do serialization always struck me as kludgey. It didn't seem foundational to have to declare serialization operators outside of your class. So I was looking for a solid answer for why.

(Note: If anyone has better Google-Fu to find a good answer already written, I'd be interested to read that.)

What I suspect is that it's technically possible, and is only a notational issue. Had the library been designed to do member overloads of << and >>, you'd have to build a line of streaming operations from right to left, instead of left to right. So instead of writing:

Rational r (1, 2);
cout << "Your rational number is " << r;

You would have to write the output line as:

r >> ("Your rational number is " >> cout);

The parentheses are needed to kick off the backwards chaining, because >> and << associate left-to-right. Without them, it will try to to find a match for r >> "Your rational number is " before "Your rational number is " >> cout. Had an operator with right-to-left associativity been picked, this could be avoided:

r >>= "Your rational number is " >>= cout;

(Note: Inside the library, non-class types like the string literal would have to be taken care of with global operator overloads.)

But is that the limit, and this reversal is pretty much inevitable for any iostream-style design that wanted serialization dispatched into the class? Am I missing any other problems?


UPDATE Perhaps a better phrasing of the "issue" is to say that I came to suspect the following:

For non-stream objects that wish to serialize themselves, the iostream library COULD hypothetically have been designed so that inserters and extracters were class members instead of global overloads...and without (significantly) affecting the runtime properties. Yet this would have ONLY worked if the iostream authors were willing to accept it would force clients to form streaming operations right-to-left.

But I lack an intuition about why global overloading an operator vs. a member could unlock an otherwise-unlockable ability to express oneself left-to-right (instead of right-to-left). Question is whether hindsight, or templates, or some esoteric feature of C++11 could offer an alternative. OR does "C++ physics" have an inherent bias for one direction over another, and global overloading is somehow the only compile-time trick in the book for overriding it.

Compare with Fleming's left hand rule for motors

Foi útil?

Solução

You get more flexibility by separating the definition of << and >> from the object you're displaying.

First of all, you might want to custom-display a non-class type, like an enum, or a pointer. Suppose you have a class Foo and you want to custom-print a pointer to Foo. You can't do it by adding a member function to Foo. Or you might want to display a templated object but only for a particular template parameter. For instance, you might want to display a vector<int> as a comma separated list, but a vector<string> as a column of string.

Another reason might be that you're not allowed or willing to modify an existing class. It's a good example of the open/closed principle in OO design, where you want your classes to be open for extension but closed for modification. Of course, your class has to expose some of its implementation without breaking encapsulation, but that's often the case (vectors expose their elements, complex exposes Re and Im, string exposes c_str, etc.).

You may even define two different overloads of << for the same class in different modules, if it makes sense.

Outras dicas

For overloading stream operators there is no restriction imposed by the Standard on whether they should be members or non-members, So Ideally they can be.In fact, most of the stream output and input operators defined by the standard library are members of the stream classes.

A Rationale:

Why inserters and extractors are not overloaded as member functions?

Usually the rule for operator overloading is:

If a binary operator changes its left operand it is usually useful to make it a member function of its left operand’s type.(Since it will usually need to access the operands private members).

By this rule Stream operators, should be implemented as members of their left operand’s type. However, their left operands are streams from the standard library, And One cannot change the standard library’s stream types. Hence these when overloading these operators for custom types, they are usually implemented as non-member functions.

I had the same question before, and looks like inserters and extracters have to be global. To me, it is ok; I just want to avoid making more "friend" functions in my class. This is how I do it, i.e., provid a public interface that "<<" can call:

class Rock {
    private:
        int weight;
        int height;
        int width;
        int length;

    public:
        ostream& output(ostream &os) const {
            os << "w" <<  weight << "hi" << height << "w" <<  width << "leng" << length << endl;
            return os;
        }
    };

    ostream& operator<<(ostream &os, const Rock& rock)  {
        return rock.output(os);
    }

Am I missing any other problems?

Not that I can think of, but I'd say that's pretty bad already.

The usual outfile << var1 << var2 << var3; is a fairly "linear" syntax. And, since we read left-to-right in both cases, the names will be in the same order as they will be in the file.

You're planning to make the syntax non-linear. A human reader will have to skip ahead and come back to see what's happening. This makes it harder. And you go even further. To read your last line, r >>= "Your rational number is " >>= cout; you first have to read forward through the >>= to see that you need to skip to the last word (or thereabouts), read ">>= cout", skip back to the beginning of the string, read forward through the string, and so on. As opposed to moving your eye(s) from one token to the next, where the brain is able to pipeline the whole process.

I have several years of experience with a language with such a non-linear syntax. I'm now looking into using clang to "compile" C++ into that language instead. (For more reasons than that, though.) If it works out, I'll be far happier.

My preferred alternative is a very minimal operator overload that only calls a member function. If optimizing even slightly, it will disappear from the produced executable anyway.

There's one "obvious" exception to the above, which is when you only have a single read/write in a statement, as in myobj >> cout;

In C++ binary operators are generally non-class members. According to The C++ Programming Language by Bjarne Stroustrup operator+ the canonical representation is a global function that first copies its left operand and then uses += onto it with the right operand, and then returns the result. So having the stream operators be global is not at all out of the ordinary. As Als mentioned, we would expect the stream operators to be members of the stream classes rather than of the data classes.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top