0

I read that, reference is returned from a overloaded assignment operator to enable operator chaining. But without that return also, operator chaining seems to work.

Can someone shed some light on this?

class A
{
    public:
        int x,y;
        char* str;

        //Default Constructor
        A(){}

        //Constructor
        A(int a, int b, char* s){
            cout<<"initialising\n";
            x = a;
            y = b;
            str = new char[10];
            str = s;
        }

        //Destructor
        ~A(){}

        //Overloaded assignment operator
        const A& operator=(const A& obj)
        {
            cout<<"Invoking Assignment Operator\n";
            x = obj.x;
            y = obj.y;
            str = new char[10];
            str = obj.str;

            //return *this;
        }
};

ostream& operator<<(ostream& os, const A& obj)
{
    os <<"X="<< obj.x<<" Y="<<obj.y<<" Str="<<obj.str<<"\n";
    return os;
}

int main()
{
    A c(3,4,"Object C");
    cout<<c;

    A d, e, f;
    d = e = f = c;  //Assignment operator invoked 3 times
    cout<<e;
}

Output:

initialising
X=3 Y=4 Str=Object C
Invoking Assignment Operator
Invoking Assignment Operator
Invoking Assignment Operator
X=3 Y=4 Str=Object C
cppcoder
  • 22,227
  • 6
  • 56
  • 81

2 Answers2

3

You're running into undefined behavior because the return type expected from operator = is const A& and you're not returning anything.

It's just unlucky that it works for you. (yes, unlucky, because undefined behavior that appears to work is the worst)

I get a compile error in MSVS, and ideone.com runs into a runtime error.

http://ideone.com/xTDb6

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • It's strange that it doesn't compile on MSVS. Not having a return statement in a function in declared as returning non-`void` is legal (syntactically and semantically valid). It's only the run time action of the flow of execution reaching the closing `}` of the function that causes UB. – CB Bailey Apr 30 '12 at 07:58
  • If `new char[10]` throws there is a legal execution path that avoids UB. IMHO MSVS is in error not to compile this. – CB Bailey Apr 30 '12 at 08:03
  • @Luchian The `ideone` link you have provided also shows runtime error, not compilation error – cppcoder Apr 30 '12 at 08:07
  • @cppcoder that's what I said. MSVS doesn't compile, ideone runs into runtime error. – Luchian Grigore Apr 30 '12 at 08:08
  • 3
    @LuchianGrigore Re "MS got it right": technically, no. The standard says that it is only undefined behavior if the function is actually called and returns. In practice, I'd prefer the error regardless of whether the function is called or not, but I've had problems in cases where the function could never return: if it's immediately obvious (e.g. a `throw` outside of any condition), then Microsoft doesn't complain, but if the body is something like `fatalError()`, Microsoft will refuse to compile the code, even though no undefined behavior is present or can occur. – James Kanze Apr 30 '12 at 08:18
2

This rule originated from code something like this:

struct Foo { 
    Foo& copy(const Foo& x) { 
        return (*this = x); 
    } 
};

At that time, two things were different about C++:

  1. The compiler-generated operator= returned an rvalue by default, and
  2. The compiler allowed a non-const reference to bind to a temporary.

The code above was intended to be equivalent to:

*this = x;
return *this;

But, it wasn't -- since operator= returned an rvalue, the compiler generated a temporary to hold the result of the assignment, then since the function returned a reference, it returned a reference to that temporary. Then, of course, things went badly in a hurry, because you now had a dangling reference to a temporary that was destroyed at the end of the full expression in which it was created. In short, a class case of returning a reference to a local -- except that it took quite a bit of analysis to realize that the local was being generated at all, not to mention a reference to it being returned.

If you define your operator= to return a value instead of a reference, it's going to have to generate a temporary, just like the compiler did in the code above. I haven't thought through the rest in detail to figure out whether the other changes in the current language would be enough to protect you in a case like this, but my immediate reaction is that you're about halfway to reconstituting that ancient bug, so unless you have absolutely no choice in the matter, I'd stay well away.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111