For C++ insertion overloading, we usually do:
ostream& operator<<(ostream& os, X& object){
// operation
return os;
}
My question here is: why it has to take a reference to the variable object of type X?
For C++ insertion overloading, we usually do:
ostream& operator<<(ostream& os, X& object){
// operation
return os;
}
My question here is: why it has to take a reference to the variable object of type X?
My question here is: why it has to take a reference to the variable object of type X?
You don't need to take a reference, it is not mandatory. In any case, take into account that, since you are not going to modify object, it would be better written as:
ostream& operator<<(ostream& os, const X& object){
// operation
return os;
}
The plain alternative is to pass object by value, by writing:
ostream& operator<<(ostream& os, X object){
// operation
return os;
}
Where is the problem? Your program will continue working, but its performance will suffer. It is not as efficient to pass a pointer to an object (that is what a reference ultimately is), than to copy the whole object. Imagine that X has the following implementation, i.e., it is actually the GpsTrack class:
/** A complete track, composed by GpsPoints. */
class GpsTrack {
public:
/** Creates a new, empty track. */
GpsTrack()
{}
/** Adds a new point to the track.
* @param p The new point.
*/
void add(const GpsPoint &p)
{ gpsPoints.push_back( p ); }
/** @return The point at position i. */
const GpsPoint &operator[](int i)
{ return gpsPoints[ i ]; }
/** @return The number of points in the track. */
int size() const
{ return gpsPoints.size(); }
/** @return A textual representation of the track. */
std::string toString() const;
/** Adds support for the << operator. */
friend std::ostream& operator<<(std::ostream& os, const GpsTrack& track)
{ os << track.toString(); return os; }
private:
std::vector<GpsPoint> gpsPoints;
};
Let's take a 32bit machine for the sake of the example. Each GpsPoint contains a pair of double
's, which are stored in 8 bytes each, so 16 bytes in total. If the GpsTrack contains 5000 points, then the result is around 78KB. This is stored in the heap transparently by std::vector
.
Say that, instead of the implementation given above, we have the following implementation for operator<<()
.
/** Adds support for the << operator. */
friend std::ostream& operator<<(std::ostream& os, GpsTrack track)
{ os << track.toString(); return os; }
Then, in order to get that track working inside operator<<()
, C++ will copy the object. The std::vector
gpsPoints will be transparently copied in another vector, duplicating its info in the heap (allocating at the beginning and deallocating at the end of the method). However, this info is made of GpsPoint's, which for the worst case (depends on its implementation), it would imply running over all points and calling their copy constructors. It is not only a waste of time, it is also a waste of resources (i.e. memory).
You can pass it as a const reference, which is as performant as a pointer and as safe (no modifications allowed) as pass-by-value.
Hope this helps.
There is no need to use a const
refererence, you can pass by value as well. Both versions work equally well
ostream& operator<<(ostream& os, const X &object);
ostream& operator<<(ostream& os, X object);
The reason for a const reference is usually the same as for other functions: for (large) objects, it is more expensive to copy (i.e. pass by value) than just pass a reference.
Another reason: const&
works even in cases where no copy constructor is available.
Well, usually we pass either by value or by const reference. Here example from the Microsoft VS2017 implementation. It passes string by const reference, by so we avoid copying and still guarantee not to change the object.
template<class _Elem,
class _Traits,
class _Alloc> inline
basic_ostream<_Elem, _Traits>& operator<<(
basic_ostream<_Elem, _Traits>& _Ostr,
const basic_string<_Elem, _Traits, _Alloc>& _Str)
{ // insert a string
return (_Insert_string(_Ostr, _Str.data(), _Str.size()));
}