2010-04-27 9 views
100

ich oft gesehen habe, dass Menschen Objekte in C++ zu erstellenBauer in c Aufruf ++ ohne neue

Thing myThing("asdf"); 

Statt dessen verwenden:

Thing myThing = Thing("asdf"); 

Dies scheint funktionieren (mit gcc), zumindest solange keine Vorlagen vorhanden sind. Meine Frage jetzt, ist die erste Zeile korrekt und wenn ja sollte ich sie verwenden?

+15

Jede Form ohne neu zu sehen. –

+10

Das zweite Formular wird den Kopierkonstruktor verwenden, also nein, sie sind nicht gleichwertig. –

+0

Ich spielte ein bisschen damit, der erste Weg scheint manchmal zu versagen, wenn Vorlagen mit parameterlosen Konstruktoren verwendet werden. – Nils

Antwort

110

Beide Zeilen sind in der Tat richtig, aber tun subtil verschiedene Dinge.

Die erste Zeile erstellt ein neues Objekt auf dem Stapel, indem ein Konstruktor des Formats Thing(const char*) aufgerufen wird.

Die zweite ist ein bisschen komplexer. Es spielt im Wesentlichen die folgenden

  1. ein Objekt von Thing Typ Erstellen Sie den Konstruktor Thing(const char*)
  2. ein Objekt von Thing Typ anlegen den Konstruktor Thing(const Thing&)
  3. Anruf ~Thing() auf dem in Schritt # erstellte Objekt mit 1
+7

Ich denke, diese Art von Aktionen sind optimiert und unterscheiden sich daher nicht signifikant in den Leistungsaspekten. –

+13

Ich glaube nicht, dass deine Schritte richtig sind. 'Ding mein Ding = Sache (...) 'verwendet den Zuweisungsoperator nicht, er ist immer noch wie ein" thing myThing (Thing (...)) "-Kopie konstruiert und beinhaltet kein standardkonstruiertes' Thing' (edit: post wurde nachträglich korrigiert) – AshleysBrain

+1

Man kann also sagen, dass die zweite Zeile falsch ist, weil Ressourcen ohne ersichtlichen Grund verschwendet werden. Natürlich ist es möglich, dass das Erstellen der ersten Instanz beabsichtigt ist für einige Nebenwirkungen, aber das ist noch schlimmer (stilistisch). –

7

Ganz einfach, beide Linien erstellen das Objekt auf dem Stapel, anstatt auf dem Haufen als 'neu'. Die zweite Zeile enthält tatsächlich einen zweiten Aufruf an einen Kopierkonstruktor, also sollte es vermieden werden (es muss auch korrigiert werden, wie in den Kommentaren angegeben). Sie sollten den Stapel für kleine Objekte so oft wie möglich verwenden, da er schneller ist. Wenn Ihre Objekte jedoch länger überleben als der Stapelrahmen, ist dies eindeutig die falsche Wahl.

27

Ich gehe davon mit der zweiten Zeile, die Sie tatsächlich bedeuten:

Thing *thing = new Thing("uiae"); 

, die der normale Weg der Schaffung neuer dynamische Objekte (notwendig für die dynamische Bindung und Polymorphismus) sein würde und die Speicherung ihrer Adresse auf einen Zeiger. Ihr Code macht das, was JaredPar beschrieben hat, nämlich das Erstellen zweier Objekte (eines übergab eine , das andere passierte eine const Thing&) und dann den Destruktor (~Thing()) auf dem ersten Objekt (const char*).

Dagegen folgt aus:

Thing thing("uiae"); 

ein statisches Objekt erzeugt, das beim Austritt aus dem aktuellen Bereich automatisch zerstört wird.

+1

Leider ist dies die am häufigsten verwendete Methode zum Erstellen neuer dynamischer Objekte anstelle von auto_ptr, unique_ptr oder verwandten. –

+1

Die Frage des OP war korrekt, diese Antwort betrifft ein anderes Problem vollständig (siehe @ JaredPar's Antwort) – Silmathoron

2

Idealerweise würde ein Compiler den zweiten optimieren, aber es ist nicht erforderlich. Der erste ist der beste Weg. Es ist jedoch sehr wichtig, den Unterschied zwischen Stack und Heap in C++ zu verstehen, da Sie Ihren eigenen Heap-Speicher verwalten müssen.

+0

Kann der Compiler garantieren, dass der Kopierkonstruktor keine Nebenwirkungen hat (wie zB E/A)? –

+0

@Stephen - es spielt keine Rolle, ob der Kopierkonstruktor I/O tut - siehe meine Antwort http://stackoverflow.com/questions/2722879/calling-constructors-in-c-witout-new/2723266#2723266 –

+0

Ok Ich sehe, der Compiler darf die zweite Form in die erste umwandeln und vermeidet dadurch den Aufruf des Kopierkonstruktors. –

16

Der Compiler möglicherweise die zweite Form in die erste Form optimieren, aber es muss nicht.

#include <iostream> 

class A 
{ 
    public: 
     A() { std::cerr << "Empty constructor" << std::endl; } 
     A(const A&) { std::cerr << "Copy constructor" << std::endl; } 
     A(const char* str) { std::cerr << "char constructor: " << str << std::endl; } 
     ~A() { std::cerr << "destructor" << std::endl; } 
}; 

void direct() 
{ 
    std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl; 
    A a(__FUNCTION__); 
    static_cast<void>(a); // avoid warnings about unused variables 
} 

void assignment() 
{ 
    std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl; 
    A a = A(__FUNCTION__); 
    static_cast<void>(a); // avoid warnings about unused variables 
} 

void prove_copy_constructor_is_called() 
{ 
    std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl; 
    A a(__FUNCTION__); 
    A b = a; 
    static_cast<void>(b); // avoid warnings about unused variables 
} 

int main() 
{ 
    direct(); 
    assignment(); 
    prove_copy_constructor_is_called(); 
    return 0; 
} 

Ausgabe von gcc 4.4:

+0

Welchen Zweck haben die statischen Modelle zum Leeren? –

+1

@Stephen Warnungen vor nicht verwendeten Variablen vermeiden. –

0

Ich spielte ein wenig damit und die Syntax scheint ziemlich seltsam zu werden, wenn ein Konstruktor keine Argumente annimmt. Lassen Sie mich ein Beispiel geben:

#include <iostream> 

using namespace std; 

class Thing 
{ 
public: 
    Thing(); 
}; 

Thing::Thing() 
{ 
    cout << "Hi" << endl; 
} 

int main() 
{ 
    //Thing myThing(); // Does not work 
    Thing myThing; // Works 

} 

so das Schreiben nur Sache Mything w/o Klammern tatsächlich den Konstruktor aufruft, während Ding Mything() den Compiler, was macht Sie wollen einen Funktionszeiger oder etwas ?? schaffen !!

+4

Dies ist eine bekannte syntaktische Mehrdeutigkeit in C++. Wenn Sie "int rand()" schreiben, kann der Compiler nicht wissen, ob Sie "create an int und default-initialize it" oder "declare function rand" meinen. Die Regel ist, dass sie die letztere wann immer möglich wählt. – jpalecek

1

In append zu JaredPar Antwort

1-üblicher Ctor, 2.-Funktion-like-Ctor mit temporärem Objekt.

diese Quelle irgendwo Kompilieren hier http://melpon.org/wandbox/ mit verschiedenen Compilern

// turn off rvo for clang, gcc with '-fno-elide-constructors' 

#include <stdio.h> 
class Thing { 
public: 
    Thing(const char*){puts(__FUNCTION__);} 
    Thing(const Thing&){puts(__FUNCTION__);} 
    ~Thing(){puts(__FUNCTION__);} 
}; 
int main(int /*argc*/, const char** /*argv*/) { 
    Thing myThing = Thing("asdf"); 
} 

Und Sie werden sehen das Ergebnis.

Von ISO/IEC 14882 2003-10-15

8,5, Teil 12

Ihr erstes werden 2. Aufbau Direkt Initialisierung

12,1, Teil 13 genannt

Eine Konvertierung der funktionalen Notation (5.2.3) kann verwendet werden, um zu erstellen neue Objekte seines Typs. [Hinweis: Die Syntax sieht wie ein expliziter Aufruf des Konstruktors aus. ] ... Ein auf diese Weise erstelltes Objekt ist unbenannt. [Anmerkung: 12.2 beschreibt die Lebensdauer von temporären Objekten. ] [Hinweis: Explizite Konstruktoraufrufe ergeben keine lvalues, siehe 3.10.

]

Wo etwa RVO lesen:

12 Spezielle Mitgliederfunktionen/12.8 Kopieren von Klassenobjekten/Part 15

Wenn bestimmte Kriterien erfüllt sind, wird eine Implementierung erlaubt wegzulassen der Kopieraufbau eines Klassenobjekts, sogar, wenn der Kopierkonstruktor und/oder destructor für das Objekt hat Nebenwirkungen.

Schalten Sie es mit Compiler-Flag von Kommentar solche Kopie-Verhalten)