1

I wrote this code

#include<iostream>
using namespace std;
class circle
{   public:
        int radius;
    public:
        circle()
        {   cout<<"object address-"<<this<<endl;
        }
        circle(int r)
        {   radius=r;
            cout<<"object address-"<<this<<" and radius is "<<this->radius<<endl;
        }
        circle operator = (circle c)
        {   cout<<"my object is "<<this<<endl;
            cout<<"my arg is "<<&c<<endl;
            radius=c.radius;
            return circle(radius);
        }
};
int main()
{   cout<<"creating circle c1 with radius 10"<<endl;
    circle c1(10);
    cout<<"creating empty circle c2"<<endl;
    circle c2;
    cout<<"creating empty circle c3"<<endl;
    circle c3;
    cout<<"c3=c2=c1"<<endl;
    c3=c2=c1;
    cout<<"Final values"<<endl<<"c1 radius-"<<c1.radius<<endl<<"c2 radius-"<<c2.radius<<endl<<"c3 radius-"<<c3.radius<<endl;
    return 0;
}

Output is

creating circle c1 with radius 10
object address-0xffffcbfc and radius is 10
creating empty circle c2
object address-0xffffcbf8
creating empty circle c3
object address-0xffffcbf4
c3=c2=c1
my object is 0xffffcbf8
my arg is 0xffffcbd8
object address-0xffffcbbc and radius is 10
my object is 0xffffcbf4
my arg is 0xffffcbd8
object address-0xffffcbbc and radius is 10
Final values
c1 radius-10
c2 radius-10
c3 radius-10

It seems that it is working.

Question 1 ) Using references is preferred to avoid creating copy of variables.Can I say that my program is alright but not efficient ?
Question 2 ) Please point out flaw in my understanding .
I think following happens
0xffffcbd8 is a copy of 0xffffcbfc (circle c1)
First c2.operator=(copy of c1) happens then radius of c2 is set to that of c1 and new object 0xffffcbbc is returned .
I was hoping that now in - c3.operator=(some circle)
some circle should be a copy of 0xffffcbbc
But , it seems original copy of circle c1 is 'some circle' which leads me to think that c3.operator=(copy of c1) is happening . Is that correct ?
It seems return value of assignment operator is not being used anywhere . Why is that ?

Thanks.

PS: please note that I know that "circle& operator = (circle& c)" instead of "circle operator = (circle c)" and "return *this" instead of "return circle(radius)" will make it alright . But I want to understand the behaviour and flaw in my code . Thanks .

19
  • 1
    I believe the right copy assignment is: circle& operator = (const circle& c) { ... ; return *this; }? Commented May 16, 2022 at 7:37
  • I would initialize the radius in the default constructor (e.g. to 0 or 1) Commented May 16, 2022 at 7:37
  • You can return a circle with another radius from the assignment operator to see, whether (that) it makes a difference. Commented May 16, 2022 at 7:40
  • even when I add radius=1 in default constructor my code works alright. Commented May 16, 2022 at 7:43
  • 1
    Obviously 0xffffcbbc and 0xfffffbf8 are temporary objects, if I well understand, what disturbs you is that you see only two temporary object addresses instead of four ? Commented May 16, 2022 at 7:59

2 Answers 2

1

Auto-Generation of special member functions

C++ has several special member functions for construction, assignment and destruction. There are rules, when those are auto-generated. Some are carefully chosen, some have historical reasons.

On the left side of the table you see, what happens to the other special member functions, if you declare some of those yourself. For the special member functions you can write an implementation or say = deleteor = default in the class definition. The table shows, which of the other ones the compiler implicitly declares as defaulted or as deleted.

There is the important rule of three/five/zero in C++: You typically have to either implement (or individually declare as deleted) three, five or zero of the special member functions (except the default constructor): https://en.cppreference.com/w/cpp/language/rule_of_three This is no fixed rule, more a rule of thumb, often pointing to bugs in the code, if it is not followed.

E.g. if you do manual resource management (e.g. opening files or allocating memory), then this has to be handled during copy as well as during destruction. Creating the two move functions is optional and can add a performance improvement (e.g. by just moving the file handle instead of actually reopening files).

The special member functions have a typical signature, but you can deviate from it. The typical (and default for auto-generated) signatures are:

T(); // default constructor
T(const T&); // copy constructor
T(T&&); // move constructor
T& operator=(const T&); // copy assignment
T& operator=(T&&); // move assignment
virtual ~T(); // destructor

Creating a different signature can lead to worse performance or unexpected functionality, but it is perfectly legal C++ and can have uses sometimes.

Going through the program output you posted on pastebin

The copy constructor is auto-generated and works in the background, you could add it with console output to get an even more complete picture, what is happening.

creating circle c1 with radius 10
object address-0x7ffc542431f0 and radius is 10
 -> the circle(int) constructor is called with radius 10

creating empty circle c2
object address-0x7ffc54243200
 -> the circle() default constructor is called, the radius is unspecified (as no default was set in the class definition nor in a member initialization list of the constructor)

creating empty circle c3
object address-0x7ffc54243210
 -> the circle() default constructor is called, the radius is unspecified

c3=c2=c1
 -> this acts like c3=(c2=c1), the right assignment is executed first

 -> in the background the copy constructor for a new circle is called (as the parameter is a value and not a reference, so we have to create the parameter as separate object) and copies c1 into a new temporary circle, the new circle has address 0x7ffc54243220 and radius 10

my object is 0x7ffc54243200
my arg is 0x7ffc54243220
 -> the copy assignment operator is called for c2 with the temporary circle object as parameter

object address-0x7ffc54243230 and radius is 20
 -> the circle(int) constructor is called from within the assignment operator to create a temporary circle with radius 20 (`return circle(20)` line)

my object is 0x7ffc54243210
my arg is 0x7ffc54243230
 -> the copy assignment operator is called on c3 with the temporary circle created in the previous return line

object address-0x7ffc54243240 and radius is 20
 -> a new temporary circle is created, which is just discarded afterwards

in destructor for object 0x7ffc54243240
in destructor for object 0x7ffc54243230
in destructor for object 0x7ffc54243220
 -> the temporary objects are destroyed

Final values
c1 radius-10
 -> c1 was not modified after destruction

c2 radius-10
 -> c2 got assigned the radius 10 from the temporary circle constructed from c1

c3 radius-20
 -> c3 got assigned the radius 20 from the temporary circle constructed in the return from the (right) assignment operator

in destructor for object 0x7ffc54243210
in destructor for object 0x7ffc54243200
in destructor for object 0x7ffc542431f0
 -> c1, c2 and c3 are destroyed at the end of main

The optimizer can remove a lot of code for creating temporary objects, if the classes are simple, if it can see the code (does not work, if circle is in a library) and whole program optimization or link time optimization is activated in the options (or your functions are declared as inline or defined in your class definition as is the case with your code instead of a separate .cpp file=translation unit). If you put console output or other non-trivial code in the special member functions - the optimizer has no choice - they have to be called in order.

C++ Insights

With C++ Insights (C++ Insights Link to your code) you can output a lot of the automatic code generation of the compiler:

  • The member function are all declared as inline as they are defined in your class definition instead of a separate translation unit.

  • In line 31 the generation of the temporary circle is wrapped into another copy constructor (the inner circle(20) as written in your code and the outer circle for creation of the parameter): return circle(circle(20)); This double creation is removed automatically. It is called copy elision (https://en.cppreference.com/w/cpp/language/copy_elision), which is mandated by the standard in some situations, one of those is returning a value, when it can be directly used.

  • In line 34 the compiler auto-generates the default copy constructor // inline constexpr circle(const circle &) noexcept = default;

  • In lines 43 and 45 you see the default constructor being called circle c2 = circle();

  • In line 47 you see the parantheses being added and the temporary circle out of c1 being created, as the parameter of the copy assignment is a value instead of a reference: c3.operator=(c2.operator=(circle(c1)));

Sign up to request clarification or add additional context in comments.

1 Comment

good line by line explanation
1

If by "alright" you mean "behaves as an unsuspecting user would expect", then no, it is not alright. There is a reason behind operator= conventionally returning *this by reference, and that reason is not efficiency. It is lack of surprise. In C++, we expect that user-defined operators should behave in a manner similar to built-in operators. Your assignment doesn't.

int x = 1, y = 2, z = 3;
(x = y) = z;  // x == 3, y == 2, z == 3

circle p(1), q(2), r(3);
(p = q) = r; // actual   : p.radius == 2, q.radius == 2, r.radius == 3
             // expected : p.radius == 3, q.radius == 2, r.radius == 3

Granted, things like (p = q) = r are rare in actual code, but this is just a test case, which your implementation fails silently (i.e. with no error message).

It is not illegal, but it is unexpected, and you need a very, very good reason to produce a component that does unexpected things. As far as I can see, you don't have one.

People can and do modify the result of assignment, and they usually expect that these modifications are applied to the assigned-to object, rather than to some temporary that will go away immediately.

1 Comment

nice explanation and good example to show weakness .

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.