Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ copy constructor causing code not to compile ( gcc )

I have the following code which doesn't compile. The compiler error is:

"error: no matching function to call B::B(B)",
candidates are B::B(B&) B::B(int)"

The code compiles under either of the following two conditions:

  1. Uncommenting the function B(const B&)
  2. change 'main' to the following

    int main()
    {
            A a;
            B b0;
            B b1 = b0;
            return 0;
    }
    

If I do 1, the code compiles, but from the output it says it's calling the 'non const copy constructor'.

Can anyone tell me what's going on here?

using namespace std;
class B
{
    public:
    int k;
     B()
     { 
        cout<<"B()"<<endl; 
     }
     B(int k) 
     { 
        cout<<"B(int)"<<endl;
        this->k = k;
     }
     /*B(const B& rhs) { 
        cout<<"Copy constructor"<<endl;
        k = rhs.k;
     }*/
     B(B& rhs) 
     { 
        cout<<"non const Copy constructor"<<endl;
        k = rhs.k;
     }
     B operator=(B& rhs)
     {
        cout<<"assign operator"<<endl;
        k = rhs.k;
        return *this;
     }  
};

class A
{
    public:
        B get_a(void)
        {
            B* a = new B(10);
            return *a;
        }       
};

int main()
{
    A a;
    B b0 = a.get_a();  // was a.just();
    B b1 = b0 ;
    return 0;
}
like image 739
Roshan Avatar asked Jul 20 '26 06:07

Roshan


2 Answers

I've done some extra reading into this, and as I suspected all along, the reason why this occurs is due to return value optimization. As the Wikipedia article explains, RVO is the allowed mechanism by which compilers are allowed to eliminate temporary objects in the process of assigning them or copying them into permanent variables. Additionally, RVO is one of the few features (if not the only) which are allowed to violate the as-if rule, whereby compilers are only allowed to make optimizations only if they have the same observable behaviours as if the optimization were never made in the first place -- an exemption which is key in explaining the behaviour here (I admittedly only learned of that exemption as I was researching this question, which is why I was also confused initially).

In your case, GCC is smart enough to avoid one of the two copies. To boil your code down to a simpler example

B returnB()
{
    B a;
    B* b = &a;
    return *b;
}

int main()
{
    B c = returnB();
    return 0;
}

If one follows the standard and does not perform RVO, two copies are made in the process of making c -- the copy of *b into returnB's return value, and the copy of the return value into c itself. In your case, GCC omits the first copy and instead makes only one copy, from *b directly into c. That also explains why B(B&) is called instead of B(const B&) -- since *b (a.k.a. a) is not a temporary value, the compiler doesn't need to use B(const B&) anymore and instead chooses the simpler B(B&) call instead when constructing c (a non-const overload is always automatically preferred over a const overload if the choice exists).

So why does the compiler still give an error if B(const B&) isn't there? That's because your code's syntax must be correct before optimizations (like RVO) can be made. In the above example, returnB() is returning a temporary (according to the C++ syntax rules), so the compiler must see a B(const B&) copy constructor. However, once your code is confirmed to be grammatically correct by the compiler, it then can make the optimization such that B(const B&) is never used anyway.

EDIT: Hat tip to Charles Bailey who found the following in the C++ standard

12.2 [class.temporary]: "Even when the creation of the temporary is avoided, all the semantic restrictions must be respected as if the temporary object was created."

which just reinforces and confirms the need for a copy constructor taking a reference to const when temporaries are to be copied for construction (irrespective of whether or not the constructor is actually used)

like image 161
GRB Avatar answered Jul 21 '26 20:07

GRB


Your get_a() function returns an object B, not a reference (but leaks the newly-created B object). Also, for assignments to work as you're doing, you need to make sure your assignment operator and copy constructors are both taking const B& arguments — then B b1 = b0 will work in this case. This works:

class B
{
public:
  int k;
  B() { cout<<"B()"<<endl; }
  B(int k) { 
    cout<<"B(int)"<<endl;
    this->k = k;
  }
  B(const B& rhs) { 
    cout<<"non const Copy constructor"<<endl;
    k = rhs.k;
  }
  B operator=(const B& rhs) {
    cout<<"assign operator"<<endl;
    k = rhs.k;
    return *this;
  }
};

class A {
public:
  B* get_a(void) {
    B* a = new B(10);
    return a;
  }
  B get_a2(void) {
    B a(10);
    return a;
  }
};

int main() {
  A a;
  B b0 = *a.get_a(); // bad: result from get_a() never freed!
  B b1 = a.get_a2(); // this works too
  return 0;
}
like image 22
cce Avatar answered Jul 21 '26 21:07

cce



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!