c++: copy by value to function params produce two objects in vs2012
Asked Answered
G

3

24

Here's code that produces different output in g++ 4.7 and vs2012 (cl17).

#include <iostream>

using namespace std;

class A
{
public:
    A() { cout << "1" << endl; }
    ~A() { cout << "2" << endl; }
};

class B : public A
{
public:
    B() { cout << "3" << endl; }
    ~B() { cout << "4" << endl; }
};

void func(A a) {}

int main()
{
    B b;
    func(b);
    return 0;
}

The GCC output is 13242, while cl outputs 132242.

Why does the cl compiler produce a second A object while it makes a copy on the stack, and for what purpose?

Gobert answered 14/11, 2012 at 8:4 Comment(15)
tested it on VS2010, the result is "132242"Battement
VS release version only produce 13242 but not debug versionHerbart
Random theory: It's copying the object once for the slice, then a second time when it copies it into the parameter list.Groundling
When you add A(const A &a) { cout << "C" << endl; } the output becomes 13C242Daytime
@KevinBallard Your random theory makes no sense. Making two copies is certainly a bugAret
@Daytime The corresponding construction may be a copy construction and/or being elided.Hawaii
@bliz Are you sure? I have tried release VS2012 and I have 132242.Daytime
132242, VS 2012 RC, build 11.0.50706.0, Win32 Release. I can even see the same output when I switched to VS 2010 toolkit (i.e. VS 2010 C++ compiler).Daytime
You should always make the destructor virtual when writing a base class, this fixes the wrong behavior...Salify
What do you get if you output the object locations (cout << this)?Luisaluise
Following gives the same output to me: int main() { func(B()); return 0; }. In this case the output can be "fixed" to expected by providing A(A &&a) { cout << "M" << endl; }Daytime
@ElektroKraut: No. Just no. Only use virtual when it's necessary. On the other hand, if not virtual, then protected is probably a good idea; and if you compile with -Wdelete-non-virtual-dtor the compiler will flag all incorrect uses for you.Leban
I've tested it with copy-ctor-logging as well, and I get proper output on both 2010 and 2012: A(),B(),A(A),~A(),~B(),~A()Lalittah
tested it on Visual Studio 6, same result, it's a bug from very very past... it's a long life bug, more than 12 years since the 1998 (when VS 6 was released)Battement
If you have an answer, please post it as such. If you have a clarification for the question, please edit the question with it. All of this useful information in the comments is clogging up that ability. Please move it to an answer.Lunneta
N
5

It seems to be a compiler bug.
The C++ Standard does not use the term Object Slicing, You are passing an object of the type B to a function which receives an parameter of the type A. The compiler will apply the usual overload resolution to find the appropriate match. In this case:
The Base class A has compiler provided copy constructor, which will take a reference to A and in absence of other conversion functions this is the best match and should be used by the compiler.

Note that if better conversion was available, it would be used. For eg: If A had a constructor A::A( B const& ), in addition to the copy constructor, then this constructor would be used, instead of the copy constructor.

Noelnoelani answered 14/11, 2012 at 8:40 Comment(5)
A explicit trivial copy constructor for A fixes the issue in VS2010, which does not make much sense to me.Unbroken
@Gorpik: More reason to believe it is a compiler bug for this corner case.The overload resolution rules are complex but this case seems to be one of the more common ones.Noelnoelani
Can one expect sense with compiler bugs? Forcing the compiler to generated a bit different symbolic representation can easily hide the bug.Daytime
@Unbroken same behavior in VS2008Hite
Downvoter: Instead of reasonless downvoting, Add an explanation why you think this answer is wrong preferably with citations from the standard and it might help the cause here.Noelnoelani
G
0

C++ compiler will synthesize the default copy constructor in the following situation. (From Inside C++ Object Model)

  1. When the class contains a member object of a class for which a copy constructor exists.
  2. When the class is derived from a base class for which a copy constructor exists.
  3. When the class declares one or more virtual functions
  4. When the class is derived from an inheritance chain in which one or more base classes are virtual.

We can see the class A is not in the 4 situations. So cl do NOT synthesize the default copy constructor for it. Maybe that's why 2 temp A objects constructed and destroyed.

From the disassemly window, We can see the following code, no A::A called. :

B b;
00B317F8  lea         ecx,[b]  
00B317FB  call        B::B (0B31650h)  
00B31800  mov         dword ptr [ebp-4],0  
func(b);
00B31807  mov         al,byte ptr [ebp-12h]  
00B3180A  mov         byte ptr [ebp-13h],al  
00B3180D  mov         byte ptr [ebp-4],1  
00B31811  movzx       ecx,byte ptr [ebp-13h]  
00B31815  push        ecx  
00B31816  call        func (0B31730h)  

But if we make the destructor virtual. We will get the following disassemble code, we can see the A::A is called. Then the result is as expected, only 1 A object created.

B b;
00331898  lea         ecx,[b]  
0033189B  call        B::B (03316A0h)  
003318A0  mov         dword ptr [ebp-4],0  
func(b);
003318A7  push        ecx  
003318A8  mov         ecx,esp  
003318AA  mov         dword ptr [ebp-1Ch],esp  
003318AD  lea         eax,[b]  
003318B0  push        eax  
003318B1  call        A::A (0331900h)  
003318B6  mov         dword ptr [ebp-20h],eax  
003318B9  call        func (03317D0h) 
Gaekwar answered 25/11, 2012 at 12:58 Comment(0)
D
-3

You encountered a compiler's bug.

The proper functionality is explained below :


The function func needs to create a copy of the object (but watch out for the slicing).

So, what happens is this :

int main()
{
    // create object B, which first creates the base object A
    B b;
    // create object A, using this copy constructor : A( const B& )
    func(b);
}

The extra ~A() call is made when the copy-constructed object A gets destroyed at the end of the func call.

Doubtless answered 14/11, 2012 at 8:14 Comment(3)
In the "cl output" there's two calls to ~A at the end of func(). That seems to be the question.Groundling
@KevinBallard I explained what should happen. I'd call the behavior a compiler's bug. Some would call it an extension ;)Aret
@Doubtless What should happen is already in the question; this is just a restatement.Unbroken

© 2022 - 2024 — McMap. All rights reserved.