Frage Warum sollte ich einen Zeiger anstelle des Objekts selbst verwenden?


Ich komme aus einem Java-Hintergrund und habe angefangen, mit Objekten in C ++ zu arbeiten. Aber eine Sache, die mir auffiel, ist, dass Leute oft Zeiger auf Objekte anstatt auf die Objekte selbst verwenden, zum Beispiel diese Deklaration:

Object *myObject = new Object;

eher, als:

Object myObject;

Oder, anstatt eine Funktion zu verwenden, sagen wir testFunc(), so was:

myObject.testFunc();

wir müssen schreiben:

myObject->testFunc();

Aber ich kann nicht herausfinden, warum wir es so machen sollten. Ich würde annehmen, dass es mit Effizienz und Geschwindigkeit zu tun hat, da wir direkten Zugriff auf die Speicheradresse haben. Habe ich recht?


1333
2018-03-03 11:54


Ursprung


Antworten:


Es ist sehr bedauerlich, dass Sie die dynamische Zuweisung so oft sehen. Das zeigt nur, wie viele schlechte C ++ - Programmierer es gibt.

In gewissem Sinne haben Sie zwei Fragen zu einem zusammengefasst. Die erste Frage ist, wann wir die dynamische Zuweisung (mit new) Die zweite ist, wann sollten wir Zeiger verwenden?

Die wichtige Botschaft von zu Hause ist, dass Sie sollten Verwenden Sie immer das entsprechende Werkzeug für den Job. In fast allen Situationen ist etwas passender und sicherer als die manuelle dynamische Zuweisung und / oder die Verwendung roher Zeiger.

Dynamische Zuordnung

In Ihrer Frage haben Sie zwei Möglichkeiten zum Erstellen eines Objekts demonstriert. Der Hauptunterschied ist die Speicherdauer des Objekts. Wenn es geht Object myObject; Innerhalb eines Blocks wird das Objekt mit der automatischen Speicherdauer erstellt. Dies bedeutet, dass es automatisch gelöscht wird, wenn es den Gültigkeitsbereich verlässt. Wenn Sie das tun new Object(), das Objekt hat eine dynamische Speicherdauer, was bedeutet, dass es bis zu Ihnen aktiv bleibt delete es. Sie sollten die dynamische Speicherdauer nur dann verwenden, wenn Sie sie benötigen. Das ist, Du solltest immer bevorzugen Sie das Erstellen von Objekten mit automatischer Speicherdauer, wenn Sie können.

Die zwei wichtigsten Situationen, in denen Sie eine dynamische Zuordnung benötigen:

  1. Sie benötigen das Objekt, um den aktuellen Umfang zu überleben - das spezifische Objekt an diesem bestimmten Speicherort, keine Kopie davon. Wenn Sie mit dem Kopieren / Verschieben des Objekts einverstanden sind (die meiste Zeit sollte es sein), sollten Sie ein automatisches Objekt bevorzugen.
  2. Sie müssen viel Speicher reservieren, die den Stapel leicht füllen kann. Es wäre schön, wenn wir uns damit nicht beschäftigen müssten (die meiste Zeit sollte es nicht sein), da es wirklich außerhalb von C ++ liegt, aber leider müssen wir uns mit der Realität der Systeme auseinandersetzen entwickeln sich für.

Wenn Sie die dynamische Zuordnung unbedingt benötigen, sollten Sie sie in einen Smart-Zeiger oder einen anderen ausführenden Typ kapseln RAII (wie die Standard-Container). Smart Pointer bieten Ownership-Semantiken von dynamisch zugewiesenen Objekten. Sieh dir das an std::unique_ptr und std::shared_ptr, beispielsweise. Wenn Sie sie entsprechend verwenden, können Sie die eigene Speicherverwaltung fast vollständig vermeiden (siehe Null-Regel).

Zeiger

Es gibt jedoch andere allgemeine Verwendungen für rohe Zeiger, die über die dynamische Zuweisung hinausgehen, aber die meisten haben Alternativen, die Sie bevorzugen sollten. Wie vorher, bevorzuge immer die Alternativen, außer du brauchst wirklich Zeiger.

  1. Sie benötigen Referenzsemantik. Manchmal möchten Sie ein Objekt mithilfe eines Zeigers übergeben (unabhängig davon, wie es zugewiesen wurde), weil die Funktion, an die Sie es übergeben, Zugriff auf das betreffende Objekt haben soll (keine Kopie davon). In den meisten Situationen sollten Sie Referenztypen jedoch den Zeigern vorziehen, da dies speziell für sie gedacht ist. Beachten Sie, dass es nicht unbedingt darum geht, die Lebensdauer des Objekts über den aktuellen Bereich hinaus zu verlängern, wie in obiger Situation 1. Wenn Sie eine Kopie des Objekts übergeben möchten, benötigen Sie wie zuvor keine Referenzsemantik.

  2. Sie brauchen Polymorphie. Sie können Funktionen nur polymorph (dh gemäß dem dynamischen Typ eines Objekts) über einen Zeiger oder eine Referenz auf das Objekt aufrufen. Wenn das das Verhalten ist, das Sie benötigen, müssen Sie Zeiger oder Referenzen verwenden. Auch hier sollten Referenzen bevorzugt werden.

  3. Sie möchten darstellen, dass ein Objekt optional ist indem man a nullptr übergeben werden, wenn das Objekt weggelassen wird. Wenn es sich um ein Argument handelt, sollten Sie lieber Standardargumente oder Funktionsüberladungen verwenden. Andernfalls sollten Sie lieber einen Typ verwenden, der dieses Verhalten kapselt, wie z std::optional (eingeführt in C ++ 17 - mit früheren C ++ - Standards, Verwendung boost::optional).

  4. Sie möchten die Übersetzungseinheiten entkoppeln, um die Übersetzungszeit zu verbessern. Die nützliche Eigenschaft eines Zeigers ist, dass Sie nur eine Vorwärtsdeklaration des angegebenen Typs benötigen (um das Objekt tatsächlich zu verwenden, benötigen Sie eine Definition). Dadurch können Sie Teile Ihres Kompilierungsprozesses entkoppeln, was die Kompilierungszeit erheblich verbessern kann. Siehe die Pimpl Idiom.

  5. Sie müssen mit einer C-Bibliothek kommunizieren oder eine C-style-Bibliothek. An diesem Punkt müssen Sie rohe Zeiger verwenden. Das Beste, was Sie tun können, ist sicherzustellen, dass Sie Ihre rohen Zeiger erst im letzten möglichen Moment loslassen. Sie können einen rohen Zeiger von einem intelligenten Zeiger abrufen, indem Sie z get Mitgliedsfunktion. Wenn eine Bibliothek eine Zuweisung für Sie vornimmt, von der erwartet wird, dass Sie die Zuordnung über ein Handle aufheben, können Sie das Handle häufig in einem intelligenten Zeiger mit einem benutzerdefinierten Löschelement umbrechen, das das Objekt entsprechend freigibt.


1346
2018-03-03 12:06



Es gibt viele Anwendungsfälle für Zeiger.

Polymorphes Verhalten. Bei polymorphen Typen werden Zeiger (oder Referenzen) verwendet, um das Schneiden zu vermeiden:

class Base { ... };
class Derived : public Base { ... };

void fun(Base b) { ... }
void gun(Base* b) { ... }
void hun(Base& b) { ... }

Derived d;
fun(d);    // oops, all Derived parts silently "sliced" off
gun(&d);   // OK, a Derived object IS-A Base object
hun(d);    // also OK, reference also doesn't slice

Referenzsemantik und Vermeiden von Kopieren. Bei nicht-polymorphen Typen vermeidet ein Zeiger (oder eine Referenz) das Kopieren eines potenziell teuren Objekts

Base b;
fun(b);  // copies b, potentially expensive 
gun(&b); // takes a pointer to b, no copying
hun(b);  // regular syntax, behaves as a pointer

Beachten Sie, dass C ++ 11 eine Bewegungssemantik hat, die viele Kopien teurer Objekte in Funktionsargumente und als Rückgabewerte vermeiden kann. Die Verwendung eines Zeigers wird diese jedoch definitiv vermeiden und mehrere Zeiger auf dasselbe Objekt zulassen (während ein Objekt nur einmal verschoben werden kann).

Ressourcenbeschaffung. Erstellen eines Zeigers zu einer Ressource mit dem new Betreiber ist ein Anti-Muster in modernem C ++. Verwenden Sie eine spezielle Ressourcenklasse (einen der Standardcontainer) oder eine intelligenter Zeiger (std::unique_ptr<> oder std::shared_ptr<>). Erwägen:

{
    auto b = new Base;
    ...       // oops, if an exception is thrown, destructor not called!
    delete b;
}

gegen

{
    auto b = std::make_unique<Base>();
    ...       // OK, now exception safe
}

Ein roher Pointer sollte nur als "View" verwendet werden und nicht in irgendeiner Weise in den Besitz einbezogen werden, sei es durch direkte Erstellung oder implizit durch Rückgabewerte. Siehe auch Dieses Q & A aus der C ++ FAQ.

Feinkörnigere Kontrolle der Lebenszeit Jedes Mal, wenn ein gemeinsamer Zeiger kopiert wird (z. B. als ein Funktionsargument), wird die Ressource, auf die er zeigt, am Leben gehalten. Reguläre Objekte (nicht erstellt von new, entweder direkt von Ihnen oder innerhalb einer Ressourcenklasse) werden zerstört, wenn sie den Gültigkeitsbereich verlassen.


155
2018-03-06 18:40



Es gibt viele ausgezeichnete Antworten auf diese Frage, einschließlich der wichtigen Anwendungsfälle von Vorwärtsdeklarationen, Polymorphismus usw., aber ich fühle, dass ein Teil der "Seele" Ihrer Frage nicht beantwortet wird - nämlich, was die verschiedenen Syntaxen in Java und C ++ bedeuten.

Lassen Sie uns die Situation untersuchen, die die zwei Sprachen vergleicht:

Java:

Object object1 = new Object(); //A new object is allocated by Java
Object object2 = new Object(); //Another new object is allocated by Java

object1 = object2; 
//object1 now points to the object originally allocated for object2
//The object originally allocated for object1 is now "dead" - nothing points to it, so it
//will be reclaimed by the Garbage Collector.
//If either object1 or object2 is changed, the change will be reflected to the other

Das nächstliegende Äquivalent dazu ist:

C ++:

Object * object1 = new Object(); //A new object is allocated on the heap
Object * object2 = new Object(); //Another new object is allocated on the heap
delete object1;
//Since C++ does not have a garbage collector, if we don't do that, the next line would 
//cause a "memory leak", i.e. a piece of claimed memory that the app cannot use 
//and that we have no way to reclaim...

object1 = object2; //Same as Java, object1 points to object2.

Sehen wir uns den alternativen C ++ Weg an:

Object object1; //A new object is allocated on the STACK
Object object2; //Another new object is allocated on the STACK
object1 = object2;//!!!! This is different! The CONTENTS of object2 are COPIED onto object1,
//using the "copy assignment operator", the definition of operator =.
//But, the two objects are still different. Change one, the other remains unchanged.
//Also, the objects get automatically destroyed once the function returns...

Der beste Weg, darüber nachzudenken, ist, dass - mehr oder weniger - Java (implizit) Zeiger auf Objekte handhabt, während C ++ entweder Zeiger auf Objekte oder die Objekte selbst behandeln kann. Es gibt Ausnahmen - wenn Sie beispielsweise Java-Primitivtypen deklarieren, handelt es sich um tatsächliche Werte, die kopiert werden, und nicht um Zeiger. Damit,

Java:

int object1; //An integer is allocated on the stack.
int object2; //Another integer is allocated on the stack.
object1 = object2; //The value of object2 is copied to object1.

Das heißt, das Verwenden von Zeigern ist NICHT notwendigerweise entweder die richtige oder die falsche Art, mit Dingen umzugehen; andere Antworten haben dies jedoch zufriedenstellend behandelt. Die allgemeine Idee ist jedoch, dass Sie in C ++ viel mehr Kontrolle über die Lebensdauer der Objekte und darüber, wo sie leben werden, haben.

Take home Punkt - die Object * object = new Object() Konstrukt ist eigentlich das, was der typischen Java-Semantik (oder C # -Syntax) am nächsten kommt.


111
2018-03-03 14:34



Ein anderer guter Grund, Zeiger zu verwenden, wäre für Vorwärtsdeklarationen. In einem ausreichend großen Projekt können sie die Kompilierzeit wirklich beschleunigen.


73
2018-03-07 07:30



Vorwort

Java ist nichts gegen C ++, im Gegensatz zum Hype. Die Java-Hype-Maschine möchte, dass Sie glauben, dass, weil Java C ++ ähnliche Syntax hat, die Sprachen ähnlich sind. Nichts kann weiter von der Wahrheit entfernt sein. Diese Fehlinformation ist einer der Gründe, warum Java-Programmierer nach C ++ gehen und Java-ähnliche Syntax verwenden, ohne die Implikationen ihres Codes zu verstehen.

Weiter gehen wir

Aber ich kann nicht herausfinden, warum wir es so machen sollten. Ich würde es annehmen   hat mit Effizienz und Schnelligkeit zu tun, da wir direkten Zugang zum Internet haben   Speicheradresse. Habe ich recht?

Im Gegenteil, eigentlich. Der Heap ist viel langsamer als der Stapel, weil der Stapel im Vergleich zum Heap sehr einfach ist. Bei automatischen Speichervariablen (auch Stack-Variablen genannt) werden ihre Destruktoren aufgerufen, sobald sie den Gültigkeitsbereich verlassen haben. Beispielsweise:

{
    std::string s;
}
// s is destroyed here

Wenn Sie jedoch einen dynamisch zugewiesenen Zeiger verwenden, muss sein Destruktor manuell aufgerufen werden. deletenennt diesen Destruktor für dich.

{
    std::string* s = new std::string;
}
delete s; // destructor called

Das hat nichts mit dem zu tun new Syntax, die in C # und Java vorherrscht. Sie werden für ganz andere Zwecke verwendet.

Vorteile der dynamischen Zuordnung

1. Sie müssen die Größe des Arrays nicht im Voraus kennen

Eines der ersten Probleme, mit denen viele C ++ - Programmierer konfrontiert sind, besteht darin, dass sie, wenn sie willkürliche Eingaben von Benutzern akzeptieren, nur eine feste Größe für eine Stapelvariable zuweisen können. Sie können die Größe von Arrays auch nicht ändern. Beispielsweise:

char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow

Natürlich, wenn Sie ein std::string stattdessen, std::string passt sich intern selbst an, so dass dies kein Problem sein sollte. Aber im Grunde ist die Lösung dieses Problems die dynamische Zuweisung. Sie können dynamischen Speicher basierend auf der Eingabe des Benutzers zuteilen, zum Beispiel:

int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];

Randnotiz: Ein Fehler, den viele Anfänger machen, ist die Verwendung von   Arrays variabler Länge. Dies ist eine GNU-Erweiterung und auch eine in Clang   weil sie viele Erweiterungen von GCC widerspiegeln. Also folgendes    int arr[n] sollte nicht verlässlich sein.

Da der Heap viel größer als der Stack ist, kann beliebig viel Speicher zugewiesen / zugewiesen werden, während der Stack eine Beschränkung hat.

2. Arrays sind keine Zeiger

Wie ist das ein Vorteil, den Sie fragen? Die Antwort wird klar, wenn Sie die Verwirrung / Mythos hinter Arrays und Zeigern verstehen. Es wird allgemein angenommen, dass sie gleich sind, aber nicht. Dieser Mythos rührt von der Tatsache her, dass Zeiger genau wie Arrays subskribiert werden können und weil Arrays in einer Funktionsdeklaration auf der obersten Ebene zu Zeigern zerfallen. Sobald ein Array jedoch zu einem Zeiger zerfällt, verliert der Zeiger seine sizeof Information. Damit sizeof(pointer) gibt die Größe des Zeigers in Bytes an, was normalerweise 8 Bytes auf einem 64-Bit-System ist.

Sie können Arrays nicht zuweisen, sondern nur initialisieren. Beispielsweise:

int arr[5] = {1, 2, 3, 4, 5}; // initialization 
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
                             // be given by the amount of members in the initializer  
arr = { 1, 2, 3, 4, 5 }; // ERROR

Auf der anderen Seite können Sie mit Zeigern machen, was Sie wollen. Da die Unterscheidung zwischen Zeigern und Arrays in Java und C # Hand-waved ist, verstehen Anfänger den Unterschied leider nicht.

3. Polymorphismus

Java und C # verfügen über Funktionen, mit denen Sie Objekte als andere Objekte behandeln können, z as Stichwort. Also wenn jemand einen behandeln wollte Entity Objekt als ein Player Objekt, könnte man tun Player player = Entity as Player; Dies ist sehr nützlich, wenn Sie Funktionen in einem homogenen Container aufrufen möchten, der nur für einen bestimmten Typ gelten soll. Die Funktionalität kann auf ähnliche Weise wie folgt erreicht werden:

std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
     auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
     if (!test) // not a triangle
        e.GenericFunction();
     else
        e.TriangleOnlyMagic();
}

Also, wenn nur Dreiecke eine Rotate-Funktion hätten, wäre es ein Compilerfehler, wenn Sie versuchen würden, ihn für alle Objekte der Klasse aufzurufen. Verwenden dynamic_castkannst du das simulieren as Stichwort. Um klar zu sein, wenn eine Umwandlung fehlschlägt, wird ein ungültiger Zeiger zurückgegeben. Damit !test ist im Grunde eine Abkürzung für die Überprüfung, ob test ist NULL oder ein ungültiger Zeiger, was bedeutet, dass die Umwandlung fehlgeschlagen ist.

Vorteile automatischer Variablen

Nachdem Sie all die großartigen Dinge gesehen haben, die die dynamische Allokation bewirken kann, werden Sie sich wahrscheinlich fragen, warum nicht jemand die dynamische Zuweisung die ganze Zeit nutzen würde? Ich habe dir schon einen Grund gesagt, der Haufen ist langsam. Und wenn Sie nicht diese ganze Erinnerung brauchen, sollten Sie sie nicht missbrauchen. Hier sind also einige Nachteile in keiner bestimmten Reihenfolge:

  • Es ist fehleranfällig. Manuelle Speicherzuweisung ist gefährlich und Sie sind anfällig für Lecks. Wenn Sie den Debugger nicht beherrschen oder valgrind (ein Speicherleck-Tool), können Sie Ihre Haare aus Ihrem Kopf ziehen. Glücklicherweise lindern RAII-Idiome und intelligente Zeiger das ein wenig, aber Sie müssen mit Praktiken wie der Regel der Drei und der Regel der Fünf vertraut sein. Es ist eine Menge Informationen, und Anfänger, die es entweder nicht wissen oder nicht, werden in diese Falle geraten.

  • Es ist nicht erforderlich. Im Gegensatz zu Java und C #, wo es idiomatisch ist, das zu verwenden new Stichwort überall in C ++, sollten Sie es nur verwenden, wenn Sie müssen. Der übliche Satz lautet, alles sieht wie ein Nagel aus, wenn du einen Hammer hast. Während Anfänger, die mit C ++ beginnen, Angst vor Zeigern haben und lernen, Stack-Variablen nach Gewohnheit, Java- und C # -Programmierern zu verwenden Anfang mit Zeigern, ohne es zu verstehen! Das geht buchstäblich auf den falschen Fuß. Sie müssen alles aufgeben, was Sie wissen, denn die Syntax ist eine Sache, das Erlernen der Sprache ist eine andere Sache.

1. (N) RVO - Aka, (benannt) Rückgabewert-Optimierung

Eine Optimierung, die viele Compiler machen, heißen Dinge Elision und Rückgabewert-Optimierung. Diese Dinge können unnötige Kopien vermeiden, was nützlich für sehr große Objekte ist, wie zum Beispiel einen Vektor, der viele Elemente enthält. Normalerweise ist es üblich, Zeiger zu verwenden Eigentum übertragen anstatt die großen Objekte zu kopieren Bewegung sie herum. Dies hat zu dem Beginn von Semantik verschieben und intelligente Zeiger.

Wenn Sie Zeiger verwenden, macht (N) RVO NICHT auftreten. Es ist vorteilhafter und weniger fehleranfällig, (N) RVO zu nutzen, als Zeiger zurückzugeben oder weiterzugeben, wenn Sie sich Sorgen um die Optimierung machen. Fehlerlecks können auftreten, wenn der Aufrufer einer Funktion dafür verantwortlich ist deleteein dynamisch zugewiesenes Objekt und dergleichen. Es kann schwierig sein, den Besitz eines Objekts zu verfolgen, wenn Zeiger wie eine heiße Kartoffel herumgereicht werden. Verwenden Sie einfach Stack-Variablen, weil es einfacher und besser ist.


62
2018-03-07 10:00



C ++ bietet Ihnen drei Möglichkeiten, ein Objekt zu übergeben: durch Zeiger, Verweis und Wert. Java begrenzt Sie mit der letzteren (die einzige Ausnahme sind primitive Typen wie int, boolean usw.). Wenn Sie C ++ nicht nur wie ein komisches Spielzeug benutzen wollen, sollten Sie den Unterschied zwischen diesen drei Möglichkeiten kennenlernen.

Java gibt vor, dass es kein Problem wie "Wer und wann sollte das zerstören?" Die Antwort ist: Der Garbage Collector, groß und schrecklich. Dennoch kann es keinen 100% igen Schutz vor Speicherlecks bieten (ja, Java kann Leckspeicher). Eigentlich gibt GC Ihnen ein falsches Gefühl der Sicherheit. Je größer Ihr SUV, desto länger ist Ihr Weg zur Evakuierungsanlage.

C ++ überlässt Ihnen die Lifecycle-Verwaltung des Objekts von Angesicht zu Angesicht. Nun, es gibt Möglichkeiten, damit umzugehen (intelligente Zeiger Familie, QObject in Qt und so weiter), aber keiner von ihnen kann in "Feuer und vergessen" Art und Weise wie GC verwendet werden: Sie sollten immerDenken Sie daran, Speicherbehandlung. Es ist nicht nur wichtig, ein Objekt zu zerstören, sondern Sie müssen auch vermeiden, dasselbe Objekt mehr als einmal zu zerstören.

Noch keine Angst? Ok: zyklische Referenzen - handle mit ihnen selbst, Mensch. Und vergiss nicht: Töte jedes Objekt genau einmal, wir C ++ Laufzeiten mögen nicht diejenigen, die sich mit Leichen anlegen, die Toten in Ruhe lassen.

Also zurück zu deiner Frage.

Wenn Sie Ihr Objekt nach Wert übergeben, nicht per Zeiger oder durch Referenz, kopieren Sie das Objekt (das ganze Objekt, ob es ein paar Bytes oder ein riesiger Datenbank-Dump ist), Sie sind schlau genug, um letzteres zu vermeiden. t du?) jedes Mal, wenn du '=' tust. Um auf die Mitglieder des Objekts zuzugreifen, verwenden Sie '.' (Punkt).

Wenn Sie Ihr Objekt per Zeiger übergeben, kopieren Sie nur ein paar Bytes (4 auf 32-Bit-Systemen, 8 auf 64-Bit-Systemen), nämlich - die Adresse dieses Objekts. Und um dies allen zu zeigen, benutzen Sie diesen ausgefallenen '->' Operator, wenn Sie auf die Mitglieder zugreifen. Oder Sie können die Kombination von '*' und '.'

Wenn Sie Verweise verwenden, erhalten Sie den Zeiger, der vorgibt, ein Wert zu sein. Es ist ein Zeiger, aber Sie greifen auf die Mitglieder über '.' Zu.

Und um noch einmal in den Bann zu kommen: Wenn Sie mehrere durch Kommas getrennte Variablen deklarieren, dann (achten Sie auf die Hände):

  • Der Typ wird jedem gegeben
  • Wert / Zeiger / Referenzmodifikator ist individuell

Beispiel:

struct MyStruct
{
    int* someIntPointer, someInt; //here comes the surprise
    MyStruct *somePointer;
    MyStruct &someReference;
};

MyStruct s1; //we allocated an object on stack, not in heap

s1.someInt = 1; //someInt is of type 'int', not 'int*' - value/pointer modifier is individual
s1.someIntPointer = &s1.someInt;
*s1.someIntPointer = 2; //now s1.someInt has value '2'
s1.somePointer = &s1;
s1.someReference = s1; //note there is no '&' operator: reference tries to look like value
s1.somePointer->someInt = 3; //now s1.someInt has value '3'
*(s1.somePointer).someInt = 3; //same as above line
*s1.somePointer->someIntPointer = 4; //now s1.someInt has value '4'

s1.someReference.someInt = 5; //now s1.someInt has value '5'
                              //although someReference is not value, it's members are accessed through '.'

MyStruct s2 = s1; //'NO WAY' the compiler will say. Go define your '=' operator and come back.

//OK, assume we have '=' defined in MyStruct

s2.someInt = 0; //s2.someInt == 0, but s1.someInt is still 5 - it's two completely different objects, not the references to the same one

21
2018-03-03 12:00



In C ++ werden auf dem Stack zugewiesene Objekte (unter Verwendung von Object object; Anweisung in einem Block) wird nur innerhalb des Gültigkeitsbereichs ausgeführt, in dem sie deklariert sind. Wenn der Codeblock die Ausführung beendet, wird das deklarierte Objekt zerstört. Wenn Sie jedoch Speicher auf Heap zuweisen, verwenden Sie Object* obj = new Object()Sie leben weiter in Haufen, bis Sie anrufen delete obj.

Ich würde ein Objekt auf Heap erstellen, wenn ich das Objekt nicht nur in dem Block des Codes verwenden möchte, der es deklariert / zugewiesen hat.


19
2018-03-03 12:19