Robert
Robert

Reputation: 71

Why do we use const and reference for arguments to copy constructors?

Consider the following example:

#include<iostream> 
using namespace std;

class Point
{
private:
    int x, y;
public:
    Point(int x1, int y1) { x = x1; y = y1; }

    // Copy constructor 
    Point(const Point& p2) { x = p2.x; y = p2.y; }

    int getX() { return x; }
    int getY() { return y; }
};

int main()
{
    Point p1(10, 15); 
    Point p2 = p1; 


    cout << "p1.x = " << p1.getX() << ", p1.y = " << p1.getY();
    cout << "\np2.x = " << p2.getX() << ", p2.y = " << p2.getY();

    return 0;
}

We notice that Point(const Point& p2); is a copy constructor. Why do we use the const keyword, and why do we use a reference? Why don't we use Point(Point p2);?

Upvotes: 0

Views: 394

Answers (1)

dfrib
dfrib

Reputation: 73186

Why do we use the const keyword [...] ?

You do not need to use a const-qualified (or even cv-qualified) argument to a class copy constructor; citing cppreference / Copy Constructors:

A copy constructor of class T is a non-template constructor whose first parameter is T&‍, const T&‍, volatile T&‍, or const volatile T&‍, and either there are no other parameters, or the rest of the parameters all have default values.

but it would be quite unusual for a copy constructor to need to mutate the object it uses for the copy-in.

#include <iostream>

struct A {
    int x;
    A(int x_) : x(x_) {}
    A(A& other) : x(other.x) { ++other.x; }
};

int main() {
    A a1{42};  // a1.x is 42.
    A a2(a1);  // a2.x is 42, AND a1.x is 43
    std::cout << a1.x << " " << a2.x;  // 43 42
}

and, moreover, a non-const reference cannot bind to rvalues to extend their lifetime, meaning such a copy constructor could not be used to copy-initialize from a temporary object. I.e., the following (common case) is well-formed:

struct A {
    int x;
    A(int x_) : x(x_) {}
    A(const A& other) : x(other.x) { }
    static A create() { return {12}; };
};

int main() {
    A a1(A::create());  // OK.
}

whereas the following is ill-formed (prior to guaranteed copy elision in C++17):

struct A {
    int x;
    A(int x_) : x(x_) {}
    A(A& other) : x(other.x) { }
    //^^ non-const ref cannot bind to rvalue.
    static A create() { return {12}; };
};

int main() {
    // Ill-formed before C++17 and guaranteed copy elision.
    A a1(A::create());  // (C++14) error: no matching constructor for initialization of 'A'
}

[...] and why do we use a reference?

If the language were to actually allow declaring a copy constructor as taking its argument (say, the copy-from object) by value instead of reference, without any other changes to the language, simply passing copy-from object to the copy constructor (before reaching the implementation of it!) would require copying the object from the call site. Thus, such a copy constructor would require copying an object prior to defining how to copy the object, a recursive logic that is naturally not sound.

Upvotes: 5

Related Questions