Warum wird der Kopierkonstruktor in diesem Fall nicht aufgerufen?

Lesezeit: 4 Minuten

Hier der kleine Codeschnipsel:

class A
{
public:
    A(int value) : value_(value)
    {
        cout <<"Regular constructor" <<endl;
    }

    A(const A& other)   : value_(other.value_)  
    {
        cout <<"Copy constructor" <<endl;
    }

private:
    int value_;
};
int main()
{
    A a = A(5);
}

Ich nahm an, dass die Ausgabe “Regular Constructor” (für RHS) gefolgt von “Copy Constructor” für LHS sein würde. Also habe ich diesen Stil vermieden und die Variable der Klasse immer als deklariert A a(5);. Aber zu meiner Überraschung wird im obigen Code der Kopierkonstruktor nie aufgerufen (Visual C++ 2008)

Weiß jemand, ob dieses Verhalten ein Ergebnis der Compiler-Optimierung oder einer dokumentierten (und portablen) Funktion von C++ ist? Danke.

  • es ist optimiert und vermeidet Konstrukt+Kopieren. Ich finde, es ist eine gute Annahme, dass kein Benutzer aus Parametern konstruieren würde, anders als beim Kopieren

    – jpinto3912

    18. November 2009 um 19:10 Uhr

  • Siehe auch: stackoverflow.com/questions/1394229/…

    – Rob Kennedy

    18. November 2009 um 19:48 Uhr

  • In g++ können Sie diese Optimierung mit der Option -fno-elide-constructors deaktivieren

    – Fred

    18. November 2009 um 23:33 Uhr

Warum wird der Kopierkonstruktor in diesem Fall nicht aufgerufen
GManNickG

Aus einem anderen Kommentar: “Also sollte ich mich standardmäßig nicht darauf verlassen (da es vom Compiler abhängen kann)”

Nein, es hängt nicht vom Compiler ab, praktisch sowieso. Jeder Compiler, der ein Sandkorn wert ist, wird keine Zeit damit verschwenden, ein A zu konstruieren und es dann zu kopieren.

In der Norm steht ausdrücklich, dass es völlig akzeptabel ist T = x; gleichbedeutend mit sagen T(x);. (§12.8.15, S. 211) Mach das mit T(T(x)) ist offensichtlich überflüssig, also entfernt es das Innere T.

Um das gewünschte Verhalten zu erhalten, würden Sie den Compiler zwingen, standardmäßig das erste A zu erstellen:

A a;
// A is now a fully constructed object,
// so it can't call constructors again:
a = A(5);

  • Danke GMan, ich benutze diese Syntax sowieso nie. Nur etwas zum Erinnern. Oh, gerade auf msdn gefunden: Der C++-Standard erlaubt das Weglassen des Kopierkonstruktors (siehe Abschnitt 12.8. Kopieren von Klassenobjekten, Absatz 15)

    – BostonLogan

    18. November 2009 um 18:56 Uhr

  • Danke, jetzt sehe ich es. Es ist ein bisschen viel, in einer Antwort zu zitieren, also werde ich es nur nach Nummer verweisen.

    – GManNickG

    18. November 2009 um 19:01 Uhr

  • Es hängt vom Compiler ab. Standard erlaubt anderes Verhalten, siehe meine Antwort.

    – Kirill W. Ljadwinski

    19. November 2009 um 5:25 Uhr

Ich habe dies recherchiert, um eine andere Frage zu beantworten, die als Dupe geschlossen wurde. Um die Arbeit nicht umsonst zu lassen, beantworte ich stattdessen diese.

Eine Erklärung des Formulars A a = A(5) wird genannt Kopie-Initialisierung der Variablen a. Der C++11-Standard 8.5/16 besagt:

Die ausgewählte Funktion wird mit dem Initialisierungsausdruck als Argument aufgerufen; Wenn die Funktion ein Konstruktor ist, initialisiert der Aufruf eine temporäre Version der CV-unqualifizierten Version des Zieltyps. Das Temporäre ist ein Prvalue. Das Ergebnis des Aufrufs (das das temporäre für den Konstruktorfall ist) wird dann verwendet, um gemäß den obigen Regeln das Objekt, das das Ziel der Kopierinitialisierung ist, direkt zu initialisieren. In bestimmten Fällen ist es einer Implementierung gestattet, das dieser Direktinitialisierung innewohnende Kopieren zu eliminieren, indem das Zwischenergebnis direkt in das zu initialisierende Objekt eingebaut wird; siehe 12.2, 12.8.

Das bedeutet, dass der Compiler nach dem geeigneten Konstruktor sucht, der verarbeitet werden soll A(5)erstellt ein temporäres und kopiert dieses temporäre in a. Aber unter welchen Umständen kann die Kopie gelöscht werden?

Mal sehen, was 12.8/31 sagt:

Wenn bestimmte Kriterien erfüllt sind, darf eine Implementierung die Copy/Move-Konstruktion eines Klassenobjekts weglassen, selbst wenn der Copy/Move-Konstruktor und/oder -Destruktor für das Objekt Seiteneffekte haben. In solchen Fällen behandelt die Implementierung die Quelle und das Ziel des ausgelassenen Kopier-/Verschiebevorgangs einfach als zwei verschiedene Arten, auf dasselbe Objekt zu verweisen, und die Zerstörung dieses Objekts erfolgt zu einem späteren Zeitpunkt, zu dem die beiden Objekte gewesen wären ohne die Optimierung zerstört. Diese Elision von Kopier-/Verschiebeoperationen, genannt Elision kopierenist unter den folgenden Umständen zulässig (die kombiniert werden können, um mehrere Kopien zu entfernen):

[…]

  • wenn ein temporäres Klassenobjekt, das nicht an eine Referenz (12.2) gebunden wurde, in ein Klassenobjekt mit demselben cv-unqualifizierten Typ kopiert/verschoben würdekann der Kopier-/Verschiebevorgang weggelassen werden, indem das temporäre Objekt direkt in das Ziel des ausgelassenen Kopierens/Verschiebens eingebaut wird

In Anbetracht dessen geschieht Folgendes mit dem Ausdruck A a = A(5):

  1. Der Compiler sieht eine Deklaration mit Copy-Initialisierung
  2. Die A(int) Der Konstruktor wird ausgewählt, um ein temporäres Objekt zu initialisieren
  3. Weil das temporäre Objekt ist nicht an eine Referenz gebunden, und es hat den gleichen Typ A Als Zieltyp im Kopierinitialisierungsausdruck darf der Compiler direkt ein Objekt hineinkonstruieren aeliminiert das Temporäre

Hier hast du Kopie-Initialisierung von a von temporär A(5). Gemäß C++-Standard 12.2/2 darf die Implementierung hier den Aufruf des Kopierkonstruktors überspringen.

  • Ich glaube nicht, dass das richtig ist. Das Beispiel in 12.2.2 beinhaltet, dass ein Temporär an eine Funktion übergeben wird, bevor das lokale Objekt konstruiert wird.

    – Michael Kristofik

    19. November 2009 um 17:42 Uhr

A a = A(5);

Diese Zeile ist äquivalent zu

A a(5);

Trotz seines funktionalen Aussehens baut die erste Zeile einfach auf a mit dem Argument 5. Es sind keine Kopien oder Provisorien beteiligt. Aus dem C++-Standard, Abschnitt 12.1.11:

Eine funktionale Notationstypumwandlung (5.2.3) kann verwendet werden, um neue Objekte ihres Typs zu erstellen. [ Note: The
syntax looks like an explicit call of the constructor. —end note ]

986130cookie-checkWarum wird der Kopierkonstruktor in diesem Fall nicht aufgerufen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy