Another base/derived problem with gcc, but not with MSVC

  • Thread starter =?ISO-8859-1?Q?Christian_Engstr=F6m?=
  • Start date
?

=?ISO-8859-1?Q?Christian_Engstr=F6m?=

When i compile the program listed below with gcc version 3.3.1 (MinGW on
Windows XP) I get the following result:

Calling 'func(d)':
'base' copy constructor

Calling 'func(*d_handle)':
'base' copy constructor

Calling 'func(d_handle)':
'derived' copy constructor <-- Why?
'base' copy constructor

The problem is the call to the 'derived' copy constructor, which I don't
want and hadn't expected, and which Microsoft Visual C++ 6.0 doesn't
generate.

Is it the gcc or the MSVC compiler that is broken? I would naively have
assumed that MSVC is right and gcc is wrong in this case, but perhaps
there is something in the standard that prescribes the gcc behavior for
some reason?

My intention was that there shouldn't be any temporary 'derived' objects
created at all, since both the implicit conversion operator from
'derived_handle' to 'derived' and 'derived's operator* return references
to an existing 'derived' object.

After having recived some expert answers on this list on a somewhat
related question, I am nowadays aware of the rule that says that a
temporary object can't be tied to a non-const reference function
parameter, but that shouldn't have anything to do with this problem,
should it?

=======================================================================
#include <iostream>

//----------------------------------------------------------------------------
class base
{
public:
base() {}
base(const base&) {std::cout << " 'base' copy constructor" <<
std::endl;}
};


//----------------------------------------------------------------------------
class derived : public base
{
public:
derived() {}
derived(const derived&) {std::cout << " 'derived' copy constructor"
<< std::endl;}
};


//----------------------------------------------------------------------------
class derived_handle
{
derived* tptr;

public:
derived_handle() {};
derived_handle(derived* inptr) : tptr(inptr) {}

derived& operator*() const {return *tptr;}
derived* operator->() const {return tptr;}

operator derived&() const {return **this;} // Implicit conversion
to 'derived'
};


//----------------------------------------------------------------------------
void func(base b) {} // Takes the parameter by value


//============================================================================
int main (int argc, char* argv[])
{
derived d;
derived_handle d_handle(new derived());

std::cout << "\nCalling 'func(d)':" << std::endl;
func(d);

std::cout << "\nCalling 'func(*d_handle)':" << std::endl;
func(*d_handle);

std::cout << "\nCalling 'func(d_handle)':" << std::endl;
func(d_handle); // Calls 'derived' copy constructor with gcc (only)

return 0;
}
 
J

John Harrison

Christian Engström said:
When i compile the program listed below with gcc version 3.3.1 (MinGW on
Windows XP) I get the following result:

Calling 'func(d)':
'base' copy constructor

Calling 'func(*d_handle)':
'base' copy constructor

Calling 'func(d_handle)':
'derived' copy constructor <-- Why?
'base' copy constructor

The problem is the call to the 'derived' copy constructor, which I don't
want and hadn't expected, and which Microsoft Visual C++ 6.0 doesn't
generate.

Is it the gcc or the MSVC compiler that is broken? I would naively have
assumed that MSVC is right and gcc is wrong in this case, but perhaps
there is something in the standard that prescribes the gcc behavior for
some reason?

My intention was that there shouldn't be any temporary 'derived' objects
created at all, since both the implicit conversion operator from
'derived_handle' to 'derived' and 'derived's operator* return references
to an existing 'derived' object.

After having recived some expert answers on this list on a somewhat
related question, I am nowadays aware of the rule that says that a
temporary object can't be tied to a non-const reference function
parameter, but that shouldn't have anything to do with this problem,
should it?

=======================================================================
#include <iostream>

//--------------------------------------------------------------------------
--
class base
{
public:
base() {}
base(const base&) {std::cout << " 'base' copy constructor" <<
std::endl;}
};


//--------------------------------------------------------------------------
--
class derived : public base
{
public:
derived() {}
derived(const derived&) {std::cout << " 'derived' copy constructor"
<< std::endl;}
};


//--------------------------------------------------------------------------
--
class derived_handle
{
derived* tptr;

public:
derived_handle() {};
derived_handle(derived* inptr) : tptr(inptr) {}

derived& operator*() const {return *tptr;}
derived* operator->() const {return tptr;}

operator derived&() const {return **this;} // Implicit conversion
to 'derived'
};


//--------------------------------------------------------------------------
--
void func(base b) {} // Takes the parameter by value


//==========================================================================
==
int main (int argc, char* argv[])
{
derived d;
derived_handle d_handle(new derived());

std::cout << "\nCalling 'func(d)':" << std::endl;
func(d);

std::cout << "\nCalling 'func(*d_handle)':" << std::endl;
func(*d_handle);

std::cout << "\nCalling 'func(d_handle)':" << std::endl;
func(d_handle); // Calls 'derived' copy constructor with gcc (only)

return 0;
}

I can't claim any expertise, I'm only answering because no-one else has.

I think you are getting into obscure areas of C++ which you would be well
advised to stay away from. Even if you could find out what the official
position was there is no guarantee that compilers would implement it
correctly.

FWIW I believe that the position is this, compilers are allow to silently
add or remove calls to copy constructors in certain circumstances (e.g.
creation of temporaries, return value optimisation) EVEN IF those copy
constructors have side effects. Essentially you must write your copy
constructors in such a way that you don't mind if they are called on the odd
occasion when you weren't expecting it (or not called when you were
expecting it).

For a definitive answer to your particular case I'd buy a copy of the C++
standard or perhaps you could try your question again in
comp.lang.c++.moderated where the high powered folks hang out.

john
 
?

=?ISO-8859-1?Q?Christian_Engstr=F6m?=

John said:
I can't claim any expertise, I'm only answering because no-one else has.

I think you are getting into obscure areas of C++ which you would be well
advised to stay away from. Even if you could find out what the official
position was there is no guarantee that compilers would implement it
correctly.

Yes, I seem to have run into a quite unlikely series of obscure compiler
problems with this one.

I *was* intending to design something that was slightly outside the
well-trodden main road, but it appears that I have walked right up to
the end of the world and that I'm beginning to fall over the edge. :)

FWIW I believe that the position is this, compilers are allow to silently
add or remove calls to copy constructors in certain circumstances (e.g.
creation of temporaries, return value optimisation) EVEN IF those copy
constructors have side effects. Essentially you must write your copy
constructors in such a way that you don't mind if they are called on the odd
occasion when you weren't expecting it (or not called when you were
expecting it).

Hm. I'm not arguing with what you say, but that would essentially mean
that the behavior of any C++ program that contains a non-trivial copy
constructor is undefined. And since most real world C++ programs do,...
For a definitive answer to your particular case I'd buy a copy of the C++
standard or perhaps you could try your question again in
comp.lang.c++.moderated where the high powered folks hang out.

john

Right, I'll try c.l.c++.moderated.

Thanks a lot for the answer, anyway!

/Christian
 
J

John Harrison

Hm. I'm not arguing with what you say, but that would essentially mean
that the behavior of any C++ program that contains a non-trivial copy
constructor is undefined. And since most real world C++ programs do,...

If the non-trivial copy constructor has a side effect then yes. But most
copy constructors (trivial or not) do nothing other than copy objects.

john
 
L

lilburne

Christian said:
Yes, I seem to have run into a quite unlikely series of obscure compiler
problems with this one.

It looks like a gcc bug. The compiler is treating the returned derived&
differently when it is returned from the conversion function

operator derived&()

to what it does when it calls dereference function:

derived& operator*()

even if operator derived&() is rewritten in the clearer form

operator derived&() const {return operator*();}

the extra constructor is called.
 
?

=?ISO-8859-1?Q?Christian_Engstr=F6m?=

lilburne said:
It looks like a gcc bug. The compiler is treating the returned derived&
differently when it is returned from the conversion function

operator derived&()

to what it does when it calls dereference function:

derived& operator*()

even if operator derived&() is rewritten in the clearer form

operator derived&() const {return operator*();}

the extra constructor is called.

Right, I have just reported to gcc it as a possible bug, and it recieved
Bugzilla number 14140.

I took the liberty of quoting your post in the bug report, I hope that
was okay.

/Christian
 
G

Gavin Deane

John Harrison said:
If the non-trivial copy constructor has a side effect then yes. But most
copy constructors (trivial or not) do nothing other than copy objects.

Are you using "undefined" in the strict, nasal-demon sense of the
word? Given this class and global variable:

int global = 0;

class foo
{
public:
foo(double d) : d_(d) {}
foo(const foo& other) : d_(other.d_) { ++global; }

private:
double d_;
};

there's no UB in copying foo objects is there? It's just that you've
no way of knowing what the value of 'global' will be after an
expression in which the copy constructor could be elided.

Since I don't write copy constructors with side effects, I am asking
purely out of academic interest.
 
J

John Harrison

Gavin Deane said:
the
odd

If the non-trivial copy constructor has a side effect then yes. But most
copy constructors (trivial or not) do nothing other than copy objects.

Are you using "undefined" in the strict, nasal-demon sense of the
word? Given this class and global variable:

int global = 0;

class foo
{
public:
foo(double d) : d_(d) {}
foo(const foo& other) : d_(other.d_) { ++global; }

private:
double d_;
};

there's no UB in copying foo objects is there? It's just that you've
no way of knowing what the value of 'global' will be after an
expression in which the copy constructor could be elided.

Since I don't write copy constructors with side effects, I am asking
purely out of academic interest.
[/QUOTE]

You're right, I think the standard calls this 'unspecified behaviour' not
undefined behaviour.

john
 

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

Members online

Forum statistics

Threads
473,768
Messages
2,569,575
Members
45,054
Latest member
LucyCarper

Latest Threads

Top