2

My copy constructor is not being called and I'm not sure why. Here's my code:

template <typename T>
class SmartPtr
{
    public:
        explicit SmartPtr(T *p) : m_p(p) { cout << "ctor" << endl; }
        SmartPtr(const SmartPtr& p) : m_p(p.m_p) { cout << "copy ctor" << endl;}

    private:
        T* m_p;
};

int main()
{
    SmartPtr<int> pt4 = SmartPtr<int>(new int);
}

The output is only "ctor". It looks like a default copy constructor is used. If I add "explicit" then it doesn't compile, giving the error:

"error: no matching function for call to ‘SmartPtr<int>::SmartPtr(SmartPtr<int>)’"

What am I doing wrong here?

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Syed H
  • 305
  • 1
  • 2
  • 9

1 Answers1

1

This is what is known as Copy Elision. It's a nice optimization where a copy clearly isn't necessary. Instead of effectively running the code:

SmartPtr<int> __tmp(new int);
SmartPtr<int> ptr4(__tmp);
__tmp.~SmartPtr<int>();

The compiler can know that __tmp only exists to construct ptr4, and thus is allowed to construct __tmp in-place in the memory owned by ptr4 as if the actual code originally run was just:

SmartPtr<int> ptr4(new int);

Note that you can tell the compiler NOT to do this too. For instance, on gcc, you can pass the -fno-elide-constructors option and with that single change (additionally logging the destructor), now your code prints:

ctor
copy ctor // not elided!
dtor      
dtor      // extra SmartPtr!

See demo.

In the standard, §12.8:

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 ...
  • In a throw-expression, when ...
  • 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
  • when the exception-declaration of an exception handler (Clause 15) ...

[Example:

class Thing {
public:
    Thing();
    ~Thing();
    Thing(const Thing&);
};

Thing f() {
    Thing t;
    return t;
}

Thing t2 = f();

Here the criteria for elision can be combined to eliminate two calls to the copy constructor of class Thing: the copying of the local automatic object t into the temporary object for the return value of function f() and the copying of that temporary object into object t2. Effectively, the construction of the local object t can be viewed as directly initializing the global object t2, and that object’s destruction will occur at program exit. Adding a move constructor to Thing has the same effect, but it is the move construction from the temporary object to t2 that is elided. —end example ]

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks. Why doesn't adding "explicit" work? It seems like the compiler doesn't bother instantiating the copy constructor. – Syed H Jan 15 '15 at 16:24
  • @SyedH Because the copy constructor is called implicitly in your code. If you want to call it explicitly, you'd have to do `SmartPtr pt4(SmartPtr(new int));` – Barry Jan 15 '15 at 16:26
  • I thought assignment at the same time as declaration uses the copy constructor. In which case, why is that line implicit conversion? What is it being converted from? Both types look the same. Anyhow, I changed the code just like you have it but the compiler still complains the same way: `no matching function for call to 'SmartPtr::SmartPtr(SmartPtr)'` – Syed H Jan 15 '15 at 16:47
  • @SyedH Shouldn't. Doesn't for me. – Barry Jan 15 '15 at 17:11
  • Bizarre. Should I create a new question for this? To be clear, my c-ctor looks like this `explicit SmartPtr(const SmartPtr& p) : m_p(p.m_p) { cout << "copy ctor" << endl;}` and the call looks like this `SmartPtr pt(SmartPtr(new int));`. The rest of the class is the same as above. I'm using **gcc version 4.1.2 20080704 (Red Hat 4.1.2-55)**. – Syed H Jan 15 '15 at 17:20
  • 1
    @SyedH Naw, likely gcc bug that they fixed sometime in the last **seven** years. Get a new compiler :) – Barry Jan 15 '15 at 17:30