GCC and copy constructor oddity


M

Martijn van Buul

Hi,

I stumbled across a strange oddity regarding copy constructors, and I was
hoping someone could explain where I'm going wrong. I tried searching google,
and looking through documentation, but couldn't find anything (or I'm blind).

I combined my test cases in http://www.stack.nl/~martijnb/constructortest.cpp
but will summarize below.

Consider a class CBar. Constructor, copy constructor and destructor have
all been implemented. As expected,

CBar a;
CBar *b = new CBar(a);

results in a call to the default constructor and the copy constructor.

However:

CBar *b = new CBar(CBar())

does *not*. Instead, it only calls the default constructor. Even more
interesting, the line above *doesn't compile* if I make CBar's copy
constructor private.

Can anyone explain me why GCC insists the copy constructor exists, but
doesn't bother calling it afterwards? I'm using GCC 4.2.1.

Kind regards,

Martijn
 
Ad

Advertisements

M

Martijn van Buul

* Martijn van Buul:
Can anyone explain me why GCC insists the copy constructor exists, but
doesn't bother calling it afterwards? I'm using GCC 4.2.1.

I'll answer myself: 12.8.15

Crap.
 
J

Joshua Maurice

Well.. to be honest, can you explain WHY then?

I don't understand either!

Take this program:

#include <iostream>
using namespace std;

class foo
{
public:
foo() { cout << "foo::foo()" << endl; }
~foo() { cout << "foo::~foo()" << endl; }
foo(foo const& ) { cout << "foo::foo(foo const& )" << endl; }
foo& operator= (foo ) { cout << "foo& foo::eek:perator= (foo )" <<
endl; return *this; }
};

foo returnByVal()
{
foo x;
return x;
}

int main()
{
foo x = returnByVal();
}

This may have as little as one constructor call and one destructor
call. It may have as many as one default constructor call, two copy
constructor calls, and three destructor calls.

In cases where an object is copy constructed from a temporary, the
compiler is allowed to remove the temporary, and initialize the object
using the same arguments that were being used to construct the
temporary.

In cases where a function returns by value, the compiler is allowed to
remove the object in the called function's code which will be
returned, and instead directly construct the temporary in the caller's
code.

These two allowances, when combined, allow a compiler to produce
efficient return-by-value. It also means you might not be making as
many copies as you expect. Thus, if you have callable copy
constructors, your copies better be "equivalent".

Interesting results from the real world:
gcc version 4.1.2 20070626
With "g++ foo.cpp", "g++ -O0 foo.cpp", and "g++ -O3 foo.cpp", the
output of the program is
foo::foo()
foo::~foo()
gcc always optimizes cases under these allowances. How odd... That
might make debugging interesting.

Visual Studios 2008, "standard debug options"
foo::foo()
foo::foo(foo const& )
foo::~foo()
foo::~foo()
"standard release options"
foo::foo()
foo::~foo()
 
J

Joshua Maurice

In cases where an object is copy constructed from a temporary, the
compiler is allowed to remove the temporary, and initialize the object
using the same arguments that were being used to construct the
temporary.

In cases where a function returns by value, the compiler is allowed to
remove the object in the called function's code which will be
returned, and instead directly construct the temporary in the caller's
code.

Sorry, let me emphasize that even if removing the copy constructor
would change visible aspects of the program, the compiler is allowed
to do this. This is not an exception under the "as if" rule. They're
two explicit exceptions, not governed by the "as if" rule, which can
change visible aspects of your program.
 
D

doublemaster007

Take this program:

#include <iostream>
using namespace std;

class foo
{
public:
  foo() { cout << "foo::foo()" << endl; }
  ~foo() { cout << "foo::~foo()" << endl; }
  foo(foo const& ) { cout << "foo::foo(foo const& )" << endl; }
  foo& operator= (foo ) { cout << "foo& foo::eek:perator= (foo )" <<
endl; return *this; }

};

foo returnByVal()
{
  foo x;
  return x;

}

int main()
{
  foo x = returnByVal();

}

This may have as little as one constructor call and one destructor
call. It may have as many as one default constructor call, two copy
constructor calls, and three destructor calls.

In cases where an object is copy constructed from a temporary, the
compiler is allowed to remove the temporary, and initialize the object
using the same arguments that were being used to construct the
temporary.

In cases where a function returns by value, the compiler is allowed to
remove the object in the called function's code which will be
returned, and instead directly construct the temporary in the caller's
code.

These two allowances, when combined, allow a compiler to produce
efficient return-by-value. It also means you might not be making as
many copies as you expect. Thus, if you have callable copy
constructors, your copies better be "equivalent".

Interesting results from the real world:
  gcc version 4.1.2 20070626
With "g++ foo.cpp", "g++ -O0 foo.cpp", and "g++ -O3 foo.cpp", the
output of the program is
  foo::foo()
  foo::~foo()
gcc always optimizes cases under these allowances. How odd... That
might make debugging interesting.

Visual Studios 2008, "standard debug options"
  foo::foo()
  foo::foo(foo const& )
  foo::~foo()
  foo::~foo()
"standard release options"
  foo::foo()
  foo::~foo()

If copy constructor is not used?? Why cant we make it private??
 
Ad

Advertisements

J

Joshua Maurice

If copy constructor is not used?? Why cant we make it private??

It might help in the future if you use grammatically correct, whole
sentences.

I can only guess you mean to ask "If the compiler will optimize out
the copy constructor, then the copy constructor will not be used. In
this situation, my compiler complains that the copy constructor is not
accessible even though it will not, in fact, be used. Why is this?"

Formal answer: Because the standard says so. The explicit exception
which allows the compiler to remove temporaries and copy constructor
calls specifically says that the compiler must still ensure the the
copy constructor exists and is accessible.

I guess a better answer is that the people who wrote the standard did
not want some piece of code valid on one compiler which elided
temporaries and copy constructors, and not valid on another compiler
which did not do this optimization. They preferred that the code would
either fail on both compilers or pass on both compilers. You know,
basically help the portability of code.
 
M

Martijn van Buul

* Joshua Maurice:
They preferred that the code would either fail on both compilers or pass on
both compilers. You know, basically help the portability of code.

However, since the outcome on different compilers can now be different should
your copy constructor have side effects (or if your copy constructor doesn't
"copy" at all!), portability is down the drain anyway.
 
B

Bo Persson

Martijn said:
* Joshua Maurice:

However, since the outcome on different compilers can now be
different should your copy constructor have side effects (or if
your copy constructor doesn't "copy" at all!), portability is down
the drain anyway.

Yes, but if I make the copy constructor private to disable copying, I
would be really annoyed if my compiler copies it anyway.


Bo Persson
 
R

Rolf Magnus

Joshua said:
I can only guess you mean to ask "If the compiler will optimize out
the copy constructor, then the copy constructor will not be used. In
this situation, my compiler complains that the copy constructor is not
accessible even though it will not, in fact, be used. Why is this?"

Formal answer: Because the standard says so. The explicit exception
which allows the compiler to remove temporaries and copy constructor
calls specifically says that the compiler must still ensure the the
copy constructor exists and is accessible.

I guess a better answer is that the people who wrote the standard did
not want some piece of code valid on one compiler which elided
temporaries and copy constructors, and not valid on another compiler
which did not do this optimization. They preferred that the code would
either fail on both compilers or pass on both compilers. You know,
basically help the portability of code.

To put it short: The correctness of code does not depend on the optimization
behavior of your compiler, and so, incorrect code must be marked as such,
even if it would be optimized away later.
 
Ad

Advertisements

R

Rolf Magnus

Martijn said:
* Joshua Maurice:

However, since the outcome on different compilers can now be different
should your copy constructor have side effects (or if your copy
constructor doesn't "copy" at all!), portability is down the drain anyway.

I would say that in a sane program, it doesn't really matter if the object
is copied or not.
 
Ad

Advertisements


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top