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?

cnd
  • 32,616
  • 62
  • 183
  • 313
user1823018
  • 211
  • 1
  • 3
  • 2
    tested it on VS2010, the result is "132242" – westwood Nov 14 '12 at 08:14
  • 4
    VS release version only produce 13242 but not debug version – billz Nov 14 '12 at 08:19
  • Random theory: It's copying the object once for the slice, then a second time when it copies it into the parameter list. – Lily Ballard Nov 14 '12 at 08:21
  • 3
    When you add A(const A &a) { cout << "C" << endl; } the output becomes 13C242 – Suma Nov 14 '12 at 08:21
  • @KevinBallard Your random theory makes no sense. Making two copies is certainly a bug – BЈовић Nov 14 '12 at 08:25
  • @Suma The corresponding construction may be a copy construction and/or being elided. – jogojapan Nov 14 '12 at 08:29
  • 1
    @bliz Are you sure? I have tried release VS2012 and I have 132242. – Suma Nov 14 '12 at 08:29
  • 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). – Suma Nov 14 '12 at 08:33
  • You should always make the destructor virtual when writing a base class, this fixes the wrong behavior... – ElektroKraut Nov 14 '12 at 08:35
  • 1
    What do you get if you output the object locations (`cout << this`)? – ecatmur Nov 14 '12 at 08:39
  • 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; } – Suma Nov 14 '12 at 08:39
  • 6
    @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. – Matthieu M. Nov 14 '12 at 08:47
  • 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() – WhozCraig Nov 14 '12 at 09:01
  • 2
    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) – westwood Nov 14 '12 at 09:22
  • 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. – George Stocker Nov 14 '12 at 12:14

3 Answers3

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.

Alok Save
  • 202,538
  • 53
  • 430
  • 533
  • 4
    A explicit trivial copy constructor for `A` fixes the issue in VS2010, which does not make much sense to me. – Gorpik Nov 14 '12 at 08:45
  • 1
    @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. – Alok Save Nov 14 '12 at 08:48
  • 1
    Can one expect sense with compiler bugs? Forcing the compiler to generated a bit different symbolic representation can easily hide the bug. – Suma Nov 14 '12 at 08:48
  • @Gorpik same behavior in VS2008 – pogorskiy Nov 14 '12 at 08:48
  • 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. – Alok Save Nov 14 '12 at 09:45
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) 
fresky
  • 530
  • 5
  • 15
-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.

BЈовић
  • 62,405
  • 41
  • 173
  • 273