Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine which copy constructors is called in C++ code

I have written a simple C++ class example with 1 non-param constructor, 1 param constructor, 2 copy constructors, 1 assignment operator and 1 plus operator.

class Complex {
protected:
    float real, img;
public:
    Complex () : real(0), img(0) {
        cout << "Default constructor\n";
    }

    Complex (float a, float b) {
        cout << "Param constructor" << a << " " << b << endl;
        real = a;
        img = b;
    }

    // 2 copy constructors
    Complex( const Complex& other ) {
        cout << "1st copy constructor " << other.real << " " << other.img << endl;
        real = other.real;
        img = other.img;
    }

    Complex( Complex& other ) {
        cout << "2nd copy constructor " << other.real << " " << other.img << endl;
        real = other.real;
        img = other.img;
    }

    // assignment overloading operator
    void operator= (const Complex& other) {
        cout << "assignment operator " << other.real << " " << other.img << endl;
        real = other.real;
        img = other.img;
    }

    // plus overloading operator
    Complex operator+ (const Complex& other) {
        cout << "plus operator " << other.real << " " << other.img << endl;
        float a = real + other.real;
        float b = img + other.img;
        return Complex(a, b);
    }

    float getReal () {
        return real;
    }

    float getImg () {
        return img;
    }
};

I used this class in main exactly like this:

int main() {
    Complex a(1,5);
    Complex b(5,7);
    Complex c = a+b; // Statement 1
    system("pause");
    return 0;
}

Result is printed as:

Param constructor 1 5
Param constructor 5 7
plus operator 5 7
Param constructor 6 12

I think a copy constructor must be used in Statement 1, but I dont really know which one is called. Please tell me which one, and why? Thanks a lot

like image 362
fatpipp Avatar asked May 19 '13 15:05

fatpipp


1 Answers

The compiler is eliding the call (actually, two calls) to the copy constructor. This is allowed (but not mandated!) per paragraph 12.8/31 of the C++11 Standard even if the constructor or destructor have side effects:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. [..] This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

— in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

[...]

— when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move

If the compiler did not elide the calls to the copy constructor, then the first one would be picked twice, i.e. the one with the following signature:

Complex( const Complex& other )

The reason is that:

  • The value returned by operator + is copy-initialized from a temporary (Complex(a, b)), and only lvalue references to const can bind to temporaries. Things would be different if operator + were written like so:

    Complex operator+ (const Complex& other) {
        // ...
        Complex c(a, b);
        return c;
    } 
    

    In this case, the second copy constructor would be called, since c is not const-qualified and is an lvalue, so it can be bound to an lvalue reference to non-const;

  • Object c in main() is being copy-constructed from an rvalue (the value returned by operator + is also a temporary). This is true no matter how operator + returns its Complex object, as long as it returns it by value. Therefore, the first copy constructor will be picked whenever copy elision is not performed.

If you are using GCC and want to verify this behavior, try setting the -fno-elide-constructors compilation flag (Clang supports it too, but version 3.2 had a bug and I don't know whether it was fixed).

like image 114
Andy Prowl Avatar answered Nov 06 '22 10:11

Andy Prowl