Why Copy Constructor Is Not Called in This Case

Why is the copy constructor not called?

Whenever a temporary object is created for the sole purpose of being copied and subsequently destroyed, the compiler is allowed to remove the temporary object entirely and construct the result directly in the recipient (i.e. directly in the object that is supposed to receive the copy). In your case

MyClass MyObj(MyClass(1, 2));

can be transformed into

MyClass MyObj(1, 2);

even if the copy constructor has side-effects.

This process is called elision of copy operation. It is described in 12.8/15 in the language standard.

Why copy constructor is not called here?

It is because copy initialization is used here, not copy constructor, due to the NRVO enabled in your compiler. You should specify

-fno-elide-constructors

flag on gcc

The C++ standard allows an implementation to omit creating a temporary
which is only used to initialize another object of the same type.
Specifying this option disables that optimization, and forces G++ to
call the copy constructor in all cases.

or compile it with cl /Od program.cpp on VS (/O1 and /O2 enables NRVO)

C++ : Avoiding copy with the “return” statement

Why the copy constructor is not called?

Copy/move elision is the only allowed exception to the so-called "as-if" rule, which normally constrains the kinds of transformations (e.g. optimizations) that a compiler is allowed to perform on a program.

The rule is intended to allow compilers to perform any optimization they wish as long as the transformed program would work "as if" it was the original one. However, there is one important exception.

Per paragraph 12.8/31 of the C++11 Standard:

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

[...]

In other words, you should never rely on a copy constructor or move constructor being called or not being called in the cases for which the provisions of 12.8/31 apply.

Why is the copy constructor not called when objects are allocated on heap?

Some detailed answer, based on you talking about optimization:

Your have the following code:

list<Guitars *> *guitarList;

void AddGuitar(int serNum, float price)
{
Guitars *x = new Guitars(serNum, price);
// Guitars x(serNum,price);
guitarList->push_back(x);
}

I think your reason to use all of those pointers is to be faster. If a Guitars object would be created on the stack as usual and then be pushed back, that would create a copy, true.

What you could do instead would be to define a move operation for Guitars and move the stack object into what you create in the list, like by calling the move constructor.

But even better would be to use std::list::emplace_back, like this:

list<Guitars> guitarList;

void AddGuitar(int serNum, float price)
{
guitarList.emplace_back(serNum, price);
}

In any case, if you talk about optimality, those pointers are not good. A pointer requires additional space, and every time the data is accessed, the pointer must be dereferenced. Also, as @PaulMcKenzie wrote in the comments, this can block the compiler from optimizing for you.

Also, making a list member itself a pointer, that is going with list<Guitars*>* guitarList; or list<Guitars>* guitarList;, is also not a good idea. The only reason I see is if you want to exchange the lists of two Inventory objects, but in that case, simply call std::swap on the lists.

If you drop the pointers, note how instantly every other code of yours becomes far easier. You don't even have to define your destructor at all.

(As for the actual question you asked, like @Jarod42 already wrote, copying pointers does not copy objects.)

(By the way, if the class Guitars represents a single guitar, then I'd go for the singular, Guitar.)

Edit:

I created a small series of tests with different ways to fill the list, using Guitars mostly unmodified. (I removed the assignments of the non-pointers to NULL though.) In any way, I did the following test setup:

#include <iostream>
#include <list>

class Guitar
{
private:
int serialNumber{0};
float price{0.0};

public:
Guitar(int serNum, float price)
{
std::cout << "Base" << std::endl;
this->serialNumber = serNum;
this->price = price;
}
Guitar(const Guitar& s)
: serialNumber{s.serialNumber}, price{s.price}
{
std::cout << "Copy" << std::endl;
}

Guitar(Guitar&& source) noexcept : serialNumber{source.serialNumber}, price{source.price}
{
std::cout << "Move" << std::endl;
}
};

void test_1()
{
std::cout << "test 1" << std::endl;
std::list<Guitar*> guitarList;
Guitar* x = new Guitar(1, 2.);
guitarList.push_back(x);
std::cout << std::endl;
}

void test_2()
{
std::cout << "test 2" << std::endl;
std::list<Guitar> guitarList;
Guitar x(1, 2.);
guitarList.push_back(x);
std::cout << std::endl;
}

void test_3()
{
std::cout << "test 3" << std::endl;
std::list<Guitar> guitarList;
guitarList.push_back(Guitar(1, 2.));
std::cout << std::endl;
}

void test_4()
{
std::cout << "test 4" << std::endl;
std::list<Guitar> guitarList;
guitarList.emplace_back(1, 2.);
std::cout << std::endl;
}

int main()
{
test_1();
test_2();
test_3();
test_4();
}

The output of this is:

test 1
Base

test 2
Base
Copy

test 3
Base
Move

test 4
Base

I hope this increases further understanding about how things work here.

The tests can be found under http://www.cpp.sh/35ld6

Also, I wanted to mention, if we talk about optimization, we'd have to talk about what we optimize. Right now, we have small lists of objects with almost no content. In that case, one would not optimize at all, as we talk about nanoseconds in difference.

The cases to think about are:

  1. A small list of big objects that are easy to move. In that case, we need to make sure that no copy constructor is called, but move would be fine.
  2. A small list of big objects that are hard to move. In that case, we only want to use the base operator, possibly by pointers as you initially did - but emplace_back also works and makes things easier. Note that the objects being hard to move would hint at a bad design for the class.
  3. A big list of small objects. Here we want to use as few constructors as possible, including move constructors. We also don't want to use a list of pointers, as that would give us additional 64 bits per object, and a lot of derefencing later on. In that case, emplace_back really shines.

So in other words, you can't go wrong with emplace_back.

Why copy constructor is not called when pass temporary object

In MyClass obj1(MyClass(5)); the compiler elides the temporary object MyClass(5) because it is allowed to.

In particular, C++ standard 2014 §12.8 para 31, defines cases when copy elision can be performed:

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):

  • 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.

Copy constructor is not called

The copy constructor is not called as your compiler has performed an optimization.

The optimization is called RVO (or NRVO) and allows the compiler to construct return values directly in place at the callsite under some circumstances.

Most compilers allow you to turn of optimizations with an option. Clang and gcc allows you to turn of elision using:

-fno-elide-constructors

Also your code has undefined behaviour as the copy constructor does not initialize s to anything. Calling Sample::show or the destructor on a copy constructed object will result in UB as accessing or deleteing uninitialized data is undefined.

Furthermore, the class Sample exhibits UB as it does not follow The Rule of Three (What is The Rule of Three?). Using the implicitly declared copy assignment operator will perform a shallow copy of s which may result in the data pointed to being deleted multiple times on destruction, which is UB.

Why is the copy constructor not called when the function returns?

With the above set-up it is quite likely that the compiler elides the copy constructor and, instead, directly constructs the temporary temp in the location where the return value will be expected. Copy elision is explicitly allowed even if the copy constructor has side effects. However, even if the copy is elided, the copy or move constructor still has to be accessible, i.e., the potential of copy elision doesn't relax the rules on the corresponding constructor to be accessible.

If you feel you absolutely want to have a copy constructor called, you can force copy construction, e.g., by passing the result through an identity function:

template <typename T>
T const& identity(T const& object) {
return object;
}
// ...
return identity(temp);

Normally, you'd want the copy constructor to be elided, however.



Related Topics



Leave a reply



Submit