2014-09-22 5 views
8

Obwohl Aufgaben werden mittels des normalen Call-by-value-Parameter Übergabemechanismus an Funktionen übergeben, die in der Theorie, schützt und dämmt die anrufende Argument, es ist immer noch möglich für eine Seite Effekt auftreten, die das Objekt als ein Argument beeinflusst oder sogar beschädigen kann. Wenn beispielsweise ein als Argument verwendetes Objekt Speicher zuweist und diesen Speicher bei der Zerstörung freigibt, gibt seine lokale Kopie innerhalb der Funktion den gleichen Speicher frei, wenn sein Destruktor aufgerufen wird. Dies wird das ursprüngliche Objekt beschädigt und effektiv nutzlos.Destructor aufgerufen, wenn Objekte von Wert übergeben werden

Das in C++ geschrieben ist: Die komplette Referenz

In diesem Programm hier

#include<iostream> 

using namespace std; 

class Sample 
{   
public: 
     int *ptr; 
     Sample(int i) 
     { 
     ptr = new int(i); 
     } 
     ~Sample() 
     { 
     cout<<"destroyed"; 
     delete ptr; 
     } 
     void PrintVal() 
     { 
     cout << "The value is " << *ptr; 
     } 
}; 
void SomeFunc(Sample x) 
{ 
cout << "Say i am in someFunc " << endl; 
} 
int main() 
{ 
Sample s1= 10; 
SomeFunc(s1); 
s1.PrintVal(); 
} 

Es einen Laufzeitfehler als das Objekt s1 erzeugt wird zerstört, wenn es von dem Objekt zurückgibt. Ich konnte nicht herausfinden, warum das passieren könnte, da eine Kopie hätte gemacht werden sollen. Ich dachte, es lag vielleicht daran, dass in der Klassendefinition kein Kopierkonstruktor vorhanden war. Aber ich war überrascht, dass, wenn diese Funktion Erklärung verwenden

void SomeFunc(Sample &x) 
{ 
cout << "Say i am in someFunc " << endl; 
} 

In dieser Erklärung kein Fehler auftritt. Sollte der Fehler auch hier auftreten, weil auf ihn verwiesen wird? Kann mir jemand erklären, was in beiden Fällen passiert.

+1

Wissen Sie, was Pass by Referenz bedeutet? – doctorlove

+0

gutes Beispiel dafür, warum Kopierkonstruktoren nützlich sind –

+2

@Claptrap Ein gutes Beispiel dafür, warum Sie nicht selbst mit dem Speicher umgehen sollten und einen intelligenten Zeiger dazu bringen sollten. Besser als die Regel von drei/fünf ist die [** Regel von Null **] (http://flamingdangerzone.com/cxx11/2012/08/15/rule-of-zero.html) – JBL

Antwort

9

Dies ist in der Tat, weil Sie keinen Kopierkonstruktor zur Verfügung gestellt haben. So wird der Compiler für Sie eine erstellen, die Trivialkopie macht. Und das ist die triviale Kopie des Zeigers, die hier problematisch ist.

Für die folgende Erklärung

void SomeFunc(Sample x); 

Es wird in der Tat eine Kopie, wenn Sie s1 an die Funktion übergeben, aber diese Kopie wird eine Kopie des Zeigers hat, das heißt, die beiden Objekte den Punkt gleich int.

Dann, wenn die Funktion verlassen wird, wird die Kopie zerstört und wird diesen Zeiger löschen, das ursprüngliche Objekt im aufrufenden Code mit einem gerade gelöschten Zeiger lassen (denken Sie daran, sie zeigen auf die gleiche Sache).

Dann wird für die folgende Erklärung

void SomeFunc(Sample &x); 

Sie haben keine Kopie, so dass das Problem nicht angezeigt. In der Tat bedeutet die Übergabe durch Referenz, dass das Sample Objekt, das Sie manipulieren, in der Funktion genau dasselbe ist wie das, das Sie an die Funktion übergeben haben, und wird nicht zerstört, wenn die Funktion beendet wird.

+0

+1 Große Antwort! Es lohnt sich immer, sich an die [Rule of 3] (http://en.cppreference.com/w/cpp/language/rule_of_three) zu erinnern –

+1

[Regel von 0] (https://blog.rmf.io/cxx11/rule -of-zero) wäre besser, wenn Sie die Regel 3 (oder 5, nach C++ 11) vermeiden können – JBL

0

wurde Ihr Objekt Feld für Feld kopiert, so innerhalb SomeFunc Sie zwei Instanzen von Sample haben - s1 und x (aber nur x zugänglich ist), und der Wert der x.ptr ist s1.ptr gleich. Dann, wenn SomeFunc endet, wird Destruktor aufgerufen und von diesem Punkt aus zeigt s1.ptr auf nicht zugeordneten Speicher. Es heißt "baumelnden Zeiger".

3

Ich gebe eine Antwort mehr aus der modernen C++ Perspektive von "vermeiden Sie rohe Zeiger, wenn Sie können". Aber ich werde auch eine wichtige Unterscheidung weisen darauf hin, sollten Sie sich bewusst sein:

C++ constructor syntax

Aber lassen Sie uns zuerst überlegen, was Ihre Absicht ist. Wenn ich schrieb:

Sample x = 1; 
Sample y = x; 

Was sollte die Semantik sein?

Sollten die Sample „Kopien“ haben jeweils ihre eigenen, unabhängigen ‚ptr‘, dessen Spitz zu Lebensdauer Objekt nur so lange, wie die Klasse hält sie in der sie leben?

Es ist in der Regel der Fall, dass Sie Zeiger überhaupt nicht benötigen, wenn dies das ist, was Sie wollen.

Die meiste Zeit, die gesamte Klassengröße wird genug, dass Stack Zuteilung ein Problem sein wird nicht sinnvoll sein, wenn man sie für sich beanspruchen, ohne new (wie Sie hier sind). Warum also Zeiger einbeziehen? Verwenden Sie einfach int i (oder was auch immer Ihre nicht-POD Klasse ist).

Wenn Sie tatsächlich die Art der Fall, wo Sie Notwendigkeit tun dynamisch zu große Datenblöcke zuweisen, um sich zu verwalten (gegen Aufschieben zu C++ Bibliothek Sammlungen oder ähnliches), konnten die exceed your stack. Wenn Sie dynamisch zuordnen müssen, müssen Sie die Konstruktion auf die eine oder andere Weise kopieren. Das bedeutet Sample muss explizit die Kopie Konstruktion verwalten -oder- verwenden Sie eine smart pointer Klasse, die es so verfeinert, dass es nicht muss.

Zunächst lassen Sie uns sagen Sie den Rohzeiger zu halten sind, das würde bedeuten:

Sample(const Sample & other) 
{ 
    ptr = new int(*other.ptr); 
} 

ABER könnten Sie das Potenzial für Fehler in dieser Situation zu reduzieren, indem eine unique_ptr stattdessen verwenden. Ein unique_ptr zerstört die Daten, auf die der rohe Zeiger zeigt, der automatisch gehalten wird, wenn der Destruktor ausgeführt wird. Sie müssen sich also nicht darum kümmern, delete anzurufen.

Auch, eine unique_ptr wird standardmäßig nicht kopieren. Also, wenn Sie nur geschrieben haben:

Die Klasse selbst kann bauen, aber Sie würden Fehler bei Ihren Callsites bekommen. Sie würden darauf hinweisen, dass Sie Kopien für etwas erstellen, für das die Kopierkonstruktion nicht richtig definiert wurde. Nicht nur das...Sie sind nicht nur machen ein Kopie in Ihrem Programm, aber zwei:

In function ‘int main()’: 
error: use of deleted function ‘Sample::Sample(const Sample&)’ 
Sample s1 = 10; 
      ^
note: ‘Sample::Sample(const Sample&)’ is implicitly deleted 
     because the default definition would be ill-formed: 

error: use of deleted function ‘Sample::Sample(const Sample&)’ 
SomeFunc(s1); 
     ^

, dass Sie ein Heads-up gibt einen Kopierkonstruktor entspricht hinzuzufügen:

 Sample(const Sample & other) 
    { 
     ptr = std::unique_ptr<int>(new int(*other.ptr)); 
    } 

Und Sie wahrscheinlich möchte Sample s1 = 10; zu Sample s1 (10); ändern, um die Kopie dort zu vermeiden. In diesem Fall könnten Sie auch wollen, dass SomeFunc seine Werte als Referenz verwendet. Ich erwähne auch die Suche in initializer lists vs assignments.

. (Anmerkung: Es gibt tatsächlich einen Namen für das Muster einer Smart-Pointer-Klasse, die Kopien clone_ptr genannt, so würden Sie nicht schreiben müssen auch dass Copykonstruktor Es ist nicht in der Standard-C++ Bibliothek aber you'll find implementations around.)

Sollen die Sample „Kopien“ ein gemeinsames dynamischen ptr, die erst nach dem letzten Verweise weggeht gelöscht wird?

Leichter mit intelligenten Zeigern, und kein Kopierkonstruktor für Sample erforderlich. Verwenden Sie eine shared_ptr. Das Standardverhalten von shared_ptr soll mit einfachen Zuweisungen kopiert werden können.

class Sample 
{   
public: 
    shared_ptr<int> ptr; 
    Sample(int i) 
    { 
     ptr = make_shared<int>(i); 
    } 
    ~Sample() 
    { 
     cout << "destroyed"; 
    } 
    void PrintVal() 
    { 
     cout << "The value is " << *ptr; 
    } 
}; 

Moral der Geschichte ist, dass desto mehr können Sie die Standardverhalten tun, um die richtige Arbeit für Sie lassen ... und desto weniger Code, den Sie schreiben ... desto weniger Potenzial haben Sie für Fehler. Es ist also gut zu wissen, was Kopierkonstrukteure machen und wann sie aufgerufen werden, aber es kann genauso wichtig sein, zu wissen, wie man sie schreibt und schreibt. nicht!

Beachten Sie, dass unique_ptr und shared_ptr aus C++ 11 stammen und #include <memory> benötigen.

Verwandte Themen