C ++ copy constructor that doesn't compile code (gcc)

I have the following code that does not compile. Compiler error:

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

      

The code compiles under one of the following two conditions:

  • Exposing function B (const B &)
  • 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 that it is calling the non-carbon 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;
}

      

+2


source to share


4 answers


I did some additional reading on this, and as I assumed, the reason this is happening has to do with return value optimization. As the Wikipedia article explainsRVO is a legal mechanism by which compilers are allowed to dispose of temporary objects in the process of assigning them or copying them to constant variables. In addition, RVO is one of the few (if not the only) functions that are allowed to violate the as-if rule, whereby compilers are only allowed to do optimization if they have the same observable behavior, as if the optimization was never done in the first place is liberation, which is key in explaining the behavior here (I admittedly only found out about this exception since I was researching this and that is why I was also confused initially).

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

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

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

      

If you follow the standard and don't do RVO, two copies are made during creation c

- a copy *b

in the returnB

return value and a copy of the return value in the c

. In your case, GCC omits the first copy and instead makes only one copy, from *b

directly to c

. This also explains why it B(B&)

is called instead B(const B&)

- since *b

(aka a

) is not a temporary value, the compiler no longer needs to use it B(const B&)

and chooses the simpler one B(B&)

instead when building c

(overloading without is const

always automatically preferred over overloading const

if a choice exists).



So why does the compiler still give an error if it B(const B&)

doesn't exist? This is because your code syntax must be correct before optimizations (such as RVO) are made. The above example returnB()

returns a temporary (as per C ++ syntax rules), so the compiler should see the copy constructor B(const B&)

. However, once your code is correctly grammatically correct by the compiler, it can optimize in such a way that it B(const B&)

never gets used.

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

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

which only reinforces and reinforces the need to create a copy constructor referencing const when temporary files need to be copied to build (regardless of whether or not the constructor is actually used)

+7


source


The function get_a()

returns an object B

, not a reference (but leaking a newly created object B

). Also, to make the jobs run the way you do, you need to make sure your assignment operator and copy constructors take arguments const B&

, and then B b1 = b0

will work in that case. It 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;
}

      

+3


source


The B b1 = b0;

culprit is the line . This line requires a call to the copy constructor. You can fix the code by writing B b1(b0)

instead, or by defining a copy constructor that accepts const B&

but does not B&

.

+1


source


Short explanation: functions that return by value create a temporary object that is treated as constant and therefore cannot be passed to functions that accept by reference, it can only be passed to functions that accept a const reference. If you do allocate an object in get_a (), you really need to return a pointer (so you don't forget to delete it, hopefully) or in the worst case, a reference. If you really want to return a copy, create an object on the stack.

Long explanation. To understand why your code won't compile when there is only a "non-content copy constructor" 1 you need to know the terms lvalues โ€‹โ€‹and rvalues. They originally meant that rvalues โ€‹โ€‹can only appear on the right side of the = (assignment) operator, while lvalues โ€‹โ€‹can only appear on the left side. Example:

T a, b;
const T ac;
a = b; // a can appear on the left of = 
b = a; // so can b => a and b are lvalues in this context
ac = a; // however, ac is const so assignment doesn't make sense<sup>2</sup>, ac is a rvalue

      

When the compiler does overload resolution (detecting which function / method overload best matches the supplied arguments), this will allow the lvalues โ€‹โ€‹to match the parameters passed by the 3 reference and reference constant types. However, it will match r values โ€‹โ€‹only for the value 3 and constant reference parameters. And this is because in a sense, since rvalues โ€‹โ€‹cannot be placed on the left side of the = operator, they have read-only semantics and should not be allowed to be changed. And when you accept a parameter via a non-const reference, it implies that you will change that parameter in some way.

The last piece of the puzzle: temporary objects are meanings. The return function creates a temporary object with a very limited lifetime. Because of its limited lifespan, it is considered a const and therefore an rvalue. And this rvalue does not match functions with parameters by non-const reference. Examples:

void f_cref(const A& a) { std::cout << "const ref" << std::endl; }
void f_ref(A& a) { std::cout << "non-const ref" << std::endl; }

A geta() { return A(); }

A a;
const A ac;
f_ref(a); // ok, a is a lvalue
f_ref(ac); // error, passing const to non-const - rvalue as lvalue - it easy to spot here
f_cref(a); // ok, you can always pass non-const to const (lvalues to rvalues)
f_ref(geta()); // error, passing temporary and therefore const object as reference
f_cref(geta()); // ok, temporary as const reference

      

Now you have all the information to figure out why your code won't compile. The copy constructor is similar to regular functions.

I've simplified things a bit, so a better, more complete and correct explanation can be found on this excellent Visual C ++ Studio Team blog post on rvalue references , which also refers to the new C ++ 0x feature "rvalue references"

1 - there is no such thing as a non-context copy constructor. The copy constructor is const, period.

2 - you can probably put a const object to the left of = if it has its operator = declared const. But that would be awful, awful, pointless.

3 - in fact, you cannot pass const A by value unless A has a copy constructor - the one that takes const A & that is.

+1


source







All Articles