4

I wrote a small test program with a sample class containing also self-defined constructor, destructor, copy constructor and assignment operator. I was surprised when I realized that the copy constructor was not called at all, even though I implemented functions with return values of my class and lines like Object o1; Object o2(o1);

innerclass.hpp:

#include <iostream>

class OuterClass
{
public:
OuterClass()
{
    std::cout << "OuterClass Constructor" << std::endl;
}
~OuterClass()
{
    std::cout << "OuterClass Destructor" << std::endl;
}
OuterClass(const OuterClass & rhs)
{
    std::cout << "OuterClass Copy" << std::endl;
}
OuterClass & operator=(const OuterClass & rhs)
{
    std::cout << "OuterClass Assignment" << std::endl;
}

class InnerClass
{
public:
    InnerClass() : m_int(0)
    {
        std::cout << "InnerClass Constructor" << std::endl;
    }
    InnerClass(const InnerClass & rhs) : m_int(rhs.m_int)
    {
        std::cout << "InnerClass Copy" << std::endl;
    }
    InnerClass & operator=(const InnerClass & rhs)
    {
        std::cout << "InnerClass Assignment" << std::endl;
        m_int = rhs.m_int;
        return *this;
    }
    ~InnerClass()
    {
        std::cout << "InnerClass Destructor" << std::endl;
    }
    void sayHello()
    {
        std::cout << "Hello!" << std::endl;
    }

private:
    int m_int;
};

InnerClass innerClass()
{
    InnerClass ic;
    std::cout << "innerClass() method" << std::endl;
    return ic;
}
};

innerclass.cpp:

#include "innerclass.hpp"

int main(void)
{
std::cout << std::endl << "1st try:" << std::endl;


OuterClass oc;
OuterClass oc2(oc);
oc.innerClass().sayHello();

std::cout << std::endl << "2nd try:" << std::endl;

OuterClass::InnerClass ic(oc.innerClass());
ic = oc.innerClass();
}

Output:

 1st try:
 OuterClass Constructor
 OuterClass Copy
 InnerClass Constructor
 innerClass() method
 Hello!
 InnerClass Destructor

 2nd try:
 InnerClass Constructor
 innerClass() method
 InnerClass Constructor
 innerClass() method
 InnerClass Assignment
 InnerClass Destructor
 InnerClass Destructor
 OuterClass Destructor
 OuterClass Destructor

After some research I read that there is no guarantee that the compiler will use the explicitely defined copy constructor. I do not understand this behavior. Why does the copy constructor even exist then, if we do not know that it is called? How does the compiler decide if it uses it?

Or, even better, is there a way to force the compiler to use the self-defined copy constructor?

Chris
  • 981
  • 4
  • 14
  • 29

6 Answers6

6

Just for completeness with the other answers, the standard allows the compiler to omit the copy constructor in certain situations (what other answers refer to as "Return Value Optimization" or "Named Return Value Optimization" - RVO/NRVO):

12.8 Copying class objects, paragraph 15 (C++98)

Whenever a temporary class object is copied using a copy constructor, and this object and the copy have the same cv-unqualified type, an implementation is permitted to treat the original and the copy as two different ways of referring to the same object and not perform a copy at all, even if the class copy constructor or destructor have side effects. For a function with a class return type, if the expression in the return statement is the name of a local object, and the cv-unqualified type of the local object is the same as the function return type, an implementation is permitted to omit creating the temporary object to hold the function return value, even if the class copy constructor or destructor has side effects. In these cases, the object is destroyed at the later of times when the original and the copy would have been destroyed without the optimization.

So in your innerClass() method, the copy constructor you might think would be called at the return is permitted to be optimized away:

InnerClass innerClass() {
    InnerClass ic;
    std::cout << "innerClass() method" << std::endl;
    return ic;    // this might not call copy ctor
}
Michael Burr
  • 333,147
  • 50
  • 533
  • 760
4

You should not design your class with a reliance on the copy constructor being called (or not called) in specific circumstances. The compiler is allowed to elide or add copy constructor calls at all sorts of places, and you really don't want to have to keep track of them.

As for why you need one - well, the compiler may decide it needs a copy, and the copy constructor is what it uses to do so. The advantage of copy constructor calls being elided is performance - copying is usually quite an expensive operation.

  • Do I understand you right... the copy constructor will maybe called and maybe not. But *if* one is needed, it will always without exception be the explicitly defined one and *not* anything auto-generated by the compiler? – Chris Apr 27 '09 at 13:56
  • 1
    In other words, do not put side-effects in your copy constructors, as the compiler has leeway to elide calls to them under certain situations – Brian Neal Apr 27 '09 at 17:39
4

I agree with Neil, you should not write a class which depends on the copy constructor being called. Namely because the compiler can do things like "Named Return Value Optimization" (link) which completely avoids the copy constructor in many return value scenarios. In order to enforce calling the copy constructor you'd need to write a lot of code aimed at "tricking" the C++ compiler. Not a good idea.

In a particular scenario though if you want to enforce calling the copy constructor, you can do an explicit call.

Object SomeFunc() {
  Object o1 = ...
  return Object(o1);
}
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • Thanks for your answer. Am I right that with your solution it is possible that the copy constructor is called twice? The first time when calling Object(o1) and the second time really returning the value? – Chris Apr 27 '09 at 14:01
  • @Chris, yes it's possible. Return value optimization is a part of the C++ compiler I am not intimately familiar with so I cannot say with certainty yes or no to a double copy. I *believe* it does though. – JaredPar Apr 27 '09 at 14:03
  • However, realize that the copy ctor might be called twice in this situation (if the compiler decides not to also perform RVO). For example, VC9 with no optimizations calls the copy ctor twice if I change the return in the innerClass() method to `return InnerClass(ic);` – Michael Burr Apr 27 '09 at 17:46
2

Is this the problem ?

OuterClass(const OuterClass & rhs)
{        
std::cout << "OuterClass Constructor" << std::endl;  
==>
std::cout << "OuterClass Copy Constructor" << std::endl;

}

OuterClass & operator=(const OuterClass & rhs)
{       
 std::cout << "OuterClass Constructor" << std::endl;
==>
 std::cout << "OuterClass Assignment operator" << std::endl;
}

A copy paste error!

I would suggest you to debug the code once to see what exactly is happening. Debugging really helps you to find the problems.

EDIT: for inner class issue:

As others have already pointed out this is the case of The Name Return Value Optimization(NRVO).

InnerClass innerClass()
{    
    InnerClass ic;        
     std::cout << "innerClass() method" << std::endl;        
   return ic;
}

the compiler can transforms the function to

void innerClass( InnerClass &namedResult)

{
std::cout << "innerClass() method" << std::endl;  

 }

Thus it eliminates both the return by value of the class object and the need to invoke the class copy constructor.

Please go through below two links to understand more on NRVO:

aJ.
  • 34,624
  • 22
  • 86
  • 128
  • Thanks, that really was the error for the "OuterClass". I did not recognize it since I was focused on the "InnerClass" where the problem still exists. – Chris Apr 27 '09 at 14:25
0

Object o1(); doesn't create any objects rather it defines a function prototype with function name o1, void arguments and return type as Object. You need to post some code to find the actual problem.

Naveen
  • 74,600
  • 47
  • 176
  • 233
  • Good point! It's "The C++ most vexing parse", as named in Scot Meyers Effective-STL book. Another example of such misleading construction: list data(istream_iterator(dataFile), istream_iterator()); –  Apr 27 '09 at 13:58
  • That one was a typo, sorry for that. – Chris Apr 27 '09 at 14:02
0

Post some code. I think you are using some incorrect syntax.

The copy constructor must exactly have the following signature:

MyObject(const MyObject&)

Then you can see if the copy constructor is called with the following code:

MyObject m1;
MyObject m2(m1);

You are not allowed to use MyObject m1(); that is a function declaration.

rlbond
  • 65,341
  • 56
  • 178
  • 228