3

I get a compile error with the following code:

main.cpp: In function âint main()â:
main.cpp:38: error: no matching function for call to âComplex::Complex(Complex)â
main.cpp:22: note: candidates are: Complex::Complex(Complex&)
main.cpp:15: note:                 Complex::Complex(double, double)

But when I change the argument type of the copy constructor to const Complex&, it works. I was thinking that the default constructor will be called with 2 Complex::Complex(2.0, 0.0) and then the copy constructor will be called to create a with a copy of Complex(2.0. 0). Isn't it correct ?

#include <iostream>
using namespace std;

class Complex {
        double re;
        double im;

public:
        Complex(double re=0, double im=0);
        Complex(Complex& c);
        ~Complex() {};
        void print();
};

Complex::Complex(double re, double im)
{
        cout << "Constructor called with " << re << " " << im << endl;
        this->re = re;
        this->im = im;
}

Complex::Complex(Complex &c)
{
        cout << "Copy constructor called " << endl;
        re = c.re;
        im = c.im;
}


void Complex::print()
{
        cout << "real = " << re << endl;
        cout << "imaginary = " << im << endl;
}

int main()
{
        Complex a = 2;
        a.print();
        Complex b = a;
        b.print();
}
4
  • how are you calling the constructor? Commented Jan 31, 2011 at 21:29
  • If it works with const, then why don't you just stick to that? Commented Jan 31, 2011 at 21:29
  • 1
    These errors are not reported by Microsoft's compiler. It performs as you expected, viz. it treats Complex a = 2 as Complex a(2,0). Other compilers may treat this as Complex a = temp, where temp is initialised. In this case, the copy constructor becomes the problem. The absence of the const modifier means that the readonly property of temp cannot be assured. Commented Jan 31, 2011 at 21:44
  • 2
    @Kevin: MS compiler is famous for ignoring the rule that forbids using temporaries as non-const reference parameters in many cases. A standard compiler should not compile the code unless the copy constructor is declared accepting a const reference. Once that ability to call the copy constructor has been verified it's however also legal for the compiler skip that call and just build the value in the destination variable avoiding a temporary. Does that sound crazy? Welcome to C++ :-) Commented Jan 31, 2011 at 22:15

4 Answers 4

9

When you write

Complex a = 2;

the compiler will not directly call the Complex constructor using 0 as default argument to build a but instead it will consider if it can "convert" 2 to a Complex.

To do the conversion it will find your Complex(re,im) version and could use that thanks to the default value and to the fact you didn't declare your constructor explicit, but then it will have to find a way transfer this value to a.

The tool for this "transfer" could be a copy constructor. However the complex value that can be built with Complex(re,im) is a temporary, and for some questionable reasons in C++ you are not allowed to pass a temporary as a non-const reference to a function.

So your copy constructor cannot be used with the temporary and the compiler is stuck as there are no ways to initialize a using 2.

If you declare your copy constructor instead accepting a const reference then the temporary can be passed to your copy constructor to initialize a and so everything works as you expect.

Directly initializing a could have been done using the syntax Complex a(2), that in this case doesn't need to use the copy constructor.

Note also that as strange it may be when you use the syntax Complex a = ... the compiler must check if it's legal to use a copy constructor, but once that legality has been checked it is allowed to not call it and use a direct initialization instead. In other words even if you need to declare your copy constructor accepting a const reference to be able to compile still the compiler may actually skip that part and directly build a without calling the copy constructor (even if the copy constructor - as in your case - has side effects). This apparently crazy rule has been added to be able to allow some optimizations in the generated code.

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

7 Comments

For completeness, it should probably be noted that this is a difference between direct (()) initialization and copy (=) initialization: the copy constructor is not used for direct initialization (well, unless the argument is of the same type as the object being initialized, obviously) so the usability checks don't take place. If we had said Complex a(2); instead of Complex a = 2;, there would be no problem.
+1: At last, an answer that actually explains why the const is required in this case!
+1 because I was a fan of the Atari 2600. Oh, you were right, too.
Thanks so much for your quick answer. I do know that a default copy constructor takes a const reference as an argument but was trying to understand if a construstor or a copy constructor would be called when I write Complex a = 2. I was expecting a default constructor and then a copy constructor to be called. So when I fix the argument type for the copy constructor, the code compiles and when I run it, the message for the default constructor is printed but I don't see a message "Copy Constructor Called". Is it using default assignment operator ?
@James McNellis: I added that explanation at the end, including a warning about the fact that once that copy construction legality has been verified, actual copy construction call can be skipped.
|
1

Copy constructors in C++ require the const part of the argument in order to take a const parameter, as you discovered. Otherwise you didn't create a copy constructor that can take a const argument, you created a copy constructor that takes a non-const Complex& as an argument.

1 Comment

A constructor for class T that takes a T& is indeed a copy constructor.
1

You always create a copy construct with & so you don't have to copy the whole object for the function to use it. Creating an object copy takes time, referencing it is much more efficient.

In any case, it is required to preceed your object parameter with const so that the copy constructor is guaranteed not to change the input object. For instance:

Complex::Complex(const Complex &c)
{
        cout << "Copy constructor called " << endl;
        re = c.re;
        im = c.im;
        c.im = 'something'; // This would not work
}

Regards,
Dennis M.

2 Comments

sorry, i updated a few seconds before this comment - wrong word choice
It seems I was wrong, see the comments of @templatetypedefs answer.
1

The problem with this code is that your copy constructor is defined with this signature:

Complex::Complex(Complex &c)

This takes a non-const reference as a parameter, which probably isn't what you want. This would mean, for example, that if you try to copy a Complex object with the copy constructor, you'd be allowed to modify the original object!

To fix this, change your code to take the Complex by const reference:

Complex::Complex(const Complex &c)

More generally, copy constructors and assignment operators should always take in their arguments by const reference unless you have a very strong reason to think otherwise.

There are a few other things in your code I should probably point out. For starters, in this case, your copy constructor isn't necessary because it just does a straight copy of all the fields. There's a rule of thumb called the "rule of three" that says that you should only have a copy constructor if you have a destructor (and then you should also have an assignment operator). Otherwise, the default functions provided by the compiler should probably be sufficient for what you're doing.

Also, there's no reason to write your own Complex class, unless you absolutely must. The <complex> header defines complex<T> as a library class.

9 Comments

Again, copy constructors have to take their arguments by const reference.
@templatetypedef: Technically, a constructor that takes a non-const reference isn't a copy constructor. It's a convert constructor. Because according to the Standard, a copy constructor has a specific signature.
@John: C++03 12.8/2 says: "A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X& or const volatile X&, and either there are no other parameters or else all other parameters have default arguments."
@Space_C0wb0y, @John: "A copy constructor for a class X is a constructor with a first parameter of type X& or of type const X&" C++03, §12.1p10.
@James: Your other comment is already gone. I did not know this. But why is that? If it is a copy constructor, then why is it not invoked?
|

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.