2012-11-14 10 views
23

Hier ist Code, der unterschiedliche Ausgabe in g ++ 4.7 und vs2012 (cl17) produziert.C++: Kopieren nach Wert zur Funktion params erzeugt zwei Objekte in vs2012

#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; 
} 

Die GCC Ausgang 13242, während cl Ausgänge 132242.

Warum erstellt der cl-Compiler ein zweites A-Objekt, während es eine Kopie auf dem Stapel erstellt und zu welchem ​​Zweck?

+2

es auf VS2010 getestet, ist das Ergebnis „132242“ – Apokal

+1

Clang 4.1 produziert „13242“ –

+4

VS Release-Version nur 13242 produzieren, aber nicht Debug-Version – billz

Antwort

-3

Sie haben einen Fehler des Compilers festgestellt.

Die richtige Funktionalität wird im Folgenden erläutert:


Die Funktion func eine Kopie des Objekts erstellen muss (aber zum Aufschneiden achten).

Also, was passiert, ist dies:

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); 
} 

Die zusätzliche ~ A() Aufruf gemacht wird, wenn die Kopier konstruiertes Objekt A am Ende des func Ruf zerstört wird.

+5

In der "cl-Ausgabe" gibt es zwei Aufrufe von '~ A' am Ende von' func() '. Das scheint die Frage zu sein. –

+0

@KevinBallard Ich erklärte, was passieren sollte. Ich würde das Verhalten einen Compiler-Fehler nennen. Manche würden es eine Erweiterung nennen;) –

+2

@BЈovieћ Was soll schon geschehen ist schon in der Frage; Das ist nur eine Neuerung. – Gorpik

5

Es scheint ein Compiler-Fehler zu sein.
Der C++ Standard verwendet nicht den Begriff Objekt Slicing, Sie übergeben ein Objekt vom Typ B an eine Funktion, die einen Parameter des Typs A empfängt. Der Compiler wendet die übliche Überladungsauflösung an, um die passende Übereinstimmung zu finden. In diesem Fall:
Die Basisklasse A verfügt über einen Compiler, der einen Kopierkonstruktor zur Verfügung stellt, der einen Verweis auf A enthält, und in Abwesenheit anderer Konvertierungsfunktionen ist dies die beste Übereinstimmung und sollte vom Compiler verwendet werden.

Beachten Sie, dass wenn eine bessere Konvertierung verfügbar wäre, würde es verwendet werden. Zum Beispiel: Wenn A neben dem Kopierkonstruktor einen Konstruktor A::A(B const&) hätte, würde dieser Konstruktor anstelle des Kopierkonstruktors verwendet werden.

+4

Ein expliziter trivialer Copy-Konstruktor für 'A' behebt das Problem in VS2010, was für mich keinen Sinn macht. – Gorpik

+1

@Gorpik: Mehr Grund zu glauben, dass es ein Compiler-Bug für diesen Eckfall ist. Die Überladungsauflösungsregeln sind komplex, aber dieser Fall scheint einer der üblicheren zu sein. –

+1

Kann man Sinn mit Compiler Bugs erwarten? Das Erzwingen, dass der Compiler eine etwas andere symbolische Darstellung erzeugt, kann den Fehler leicht verbergen. – Suma

0

Der C++ - Compiler wird den Standardkopiekonstruktor in der folgenden Situation synthetisieren. (Aus dem C++ - Objektmodell)

  1. Wenn die Klasse ein Memberobjekt einer Klasse enthält, für die ein Kopierkonstruktor existiert.
  2. Wenn die Klasse von einer Basisklasse abgeleitet ist, für die ein Kopierkonstruktor existiert.
  3. Wenn die Klasse eine oder mehrere virtuelle Funktionen deklariert
  4. Wenn die Klasse von einer Vererbungskette abgeleitet ist, in der eine oder mehrere Basisklassen virtuell sind.

Wir können sehen, die Klasse A ist nicht in den 4 Situationen. Also cl nicht den Standard-Copy-Konstruktor dafür synthetisieren. Vielleicht deshalb 2 Temp A-Objekte konstruiert und zerstört.

Aus dem Disassmly-Fenster können wir den folgenden Code sehen, kein A :: A genannt.:

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) 

Aber wenn wir den Destruktor virtuell machen. Wir werden den folgenden Zerlegungscode bekommen, wir können sehen, dass der A :: A aufgerufen wird. Dann ist das Ergebnis wie erwartet, nur 1 Ein Objekt erstellt.

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)