13.12 — The copy constructor

Recapping the types of initialization

Since we’re going to talk a lot about initialization in the next few lessons, let’s first recap the types of initialization that C++ supports: direct initialization, uniform initialization or copy initialization.

Here are examples of all of those, using our Fraction class:

We can do a direct initialization:

In C++11, we can do a uniform initialization:

And finally, we can do a copy initialization:

With direct and uniform initialization, the object being created is directly initialized. However, copy initialization is a little more complicated. We’ll explore copy initialization in more detail in the next lesson. But in order to do that effectively, we need to take a short detour.

The copy constructor

Now consider the following program:

If you compile this program, you’ll see that it compiles just fine, and produces the result:

Let’s take a closer look at how this program works.

The initialization of variable fiveThirds is just a standard direct initialization that calls the Fraction(int, int) constructor. No surprises there. But what about the next line? The initialization of variable fCopy is also clearly a direct initialization, and you know that constructor functions are used to initialize classes. So what constructor is this line calling?

The answer is that this line is calling Fraction’s copy constructor. A copy constructor is a special type of constructor used to create a new object as a copy of an existing object. And much like a default constructor, if you do not provide a copy constructor for your classes, C++ will create a public copy constructor for you. Because the compiler does not know much about your class, by default, the created copy constructor utilizes a method of initialization called memberwise initialization. Memberwise initialization simply means that each member of the copy is initialized directly from the member of the class being copied. In the above example, fCopy.m_numerator would be initialized from fiveThirds.m_numerator, etc…

Just like we can explicitly define a default constructor, we can also explicitly define a copy constructor. The copy constructor looks just like you’d expect it to:

When this program is run, you get:

Copy constructor called

The copy constructor we defined in the example above uses memberwise initialization, and is functionally equivalent to the one we’d get by default, except we’ve added an output statement to prove the copy constructor is being called.

Unlike with default constructors, it’s fine to use the default copy constructor if it meets your needs.

One interesting note: You’ve already seen a few examples of overloaded operator<<, where we’re able to access the private members of parameter f1 because the function is a friend of the Fraction class. Similarly, member functions of a class can access the private members of parameters of the same class type. Since our Fraction copy constructor takes a parameter of the class type (to make a copy of), we’re able to access the members of parameter fraction directly, even though it’s not the implicit object.

Preventing copies

We can prevent copies of our classes from being made by making the copy constructor private:

Now when we try to compile our program, we’ll get a compile error since fCopy needs to use the copy constructor, but can not see it since the copy constructor has been declared as private.

The copy constructor may be elided

Now consider the following example:

Consider how this program works. First, we direct initialize an anonymous Fraction object, using the Fraction(int, int) constructor. Then we use that anonymous Fraction object as an initializer for Fraction fiveThirds. Since the anonymous object is a Fraction, as is fiveThirds, this should call the copy constructor, right?

Run this and compile it for yourself. You’d probably expect to get this result (and you may):

copy constructor called

But in actuality, you’re more likely to get this result:


Why didn’t our copy constructor get called?

Note that initializing an anonymous object and then using that object to direct initialize our defined object takes two steps (one to create the anonymous object, one to call the copy constructor). However, the end result is essentially identical to just doing a direct initialization, which only takes one step.

For this reason, in such cases, the compiler is allowed to opt out of calling the copy constructor and just do a direct initialization instead. This process is called elision.

So although you wrote:

The compiler may change this to:

which only requires one constructor call (to Fraction(int, int)). Note that in cases where elision is used, any statements in the body of the copy constructor are not executed, even if they would have produced side effects (like printing to the screen)!

Prior to C++17, copy elision is an optimization the compiler can make. As of C++17, some cases of copy elision (including the example above) have been made mandatory.

Finally, note that if you make the copy constructor private, any initialization that would use the copy constructor will cause a compile error, even if the copy constructor is elided!

13.13 -- Copy initialization
13.11 -- Overloading typecasts

86 comments to 13.12 — The copy constructor

  • Kacper Bazan

    Hey there! Should the constructors for the Fraction class use braces for initializing member variables?

  • What an amazing blog…hope it will be enough to get more positive result and looks like a good starting point!

  • As a fresher, I gained a perspective regarding dot net. Very informative and very well explained.

  • J34NP3T3R

    elision makes sense to me

    since it doesn't make any sense to do

    which you could have easily made

  • I liked your article. Thanks for providing such a piece of knowledgeable information.

  • Kalcifer

    If the elision is mandatory, the copy constructors need not be present or accessible.

  • Zeratul

    Hey! Thank you for your work, Alex
    Have a question for this line:
    > Unlike with default constructors (where you should always provide your own default constructor)

    Why do we need to always provide our own default constructor? Haven't found an answer to this in comments or the Internet. Can we just omit writing any constructor and have a default one generated for us, if we are fine with it? E.g. (from final quiz to this chapter):

  • Arnold

    Question for the Last Sentence:

    I know it sounds stupid but I made the copy constructor private and still executed fine(no compile error). I cant believe this happens only to me.

  • Gowtham

    Why did the first one output '7/1'?

    • nascardriver

      `(10, 7)` is using the comma operator. The 10 is discarded, the expression evaluates to `7`, you're calling `Fraction(7)` which resolves to `Fraction(7, 1)`

  • Passionate_programmer

    >>Finally, note that if you make the copy constructor private, any initialization that would use the copy constructor will cause a compile error, even if the copy constructor is elided!

    Is this because for example for the code below, the compiler at this step is unable to distinguish this direct initialization is going to be an elision?

    Another question, which one is executed first? I mean first the commands in the body of constructors, here is 'assert', are executed and then initialization happens or vice versa?

  • Passionate_programmer

    >>Memberwise initialization simply means that each member of the COPY is initialized directly from the member of the class being copied. In the above example, fCopy.m_numerator would be initialized from fiveThirds.m_numerator, etc…
    can we change 'copy' to ' the copy object' as I have read that line several times to be able to possess it mostly because  I had thought 'copy' referred to  'copy constructor'.

    >>the created copy constructor utilizes a method of initialization
    Do you mean "the default copy constructor'?

  • Rushhahb


    Is it like an 'operator typecast' mentioned in the previous lesson? if not, how does the compiler do this?

    • nascardriver

      No assignment operator is used during construction. The assignment operator is only used for assignments. When you create a new object, the compiler considers constructors of the created type (`Fraction`) and conversion operators of the value (`7`).

  • well explained………
    gets lot of information after reading your blog….
    thanks for sharing…..keep updating

Leave a Comment

Put all code inside code tags: [code]your code here[/code]