[c++] Warum sollte ich einen Zeiger anstelle des Objekts selbst verwenden?


Answers

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 Einen Zeiger auf eine Ressource mit dem new Operator zu erstellen, ist ein Anti-Pattern im modernen C ++. Verwenden Sie eine spezielle Ressourcenklasse (einen der Standardcontainer) oder einen Smartpointer ( 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 .

Feinere Lebenszeitsteuerung Jedes Mal, wenn ein gemeinsamer Zeiger kopiert wird (z. B. als Funktionsargument), wird die Ressource, auf die er zeigt, am Leben erhalten. Reguläre Objekte (die nicht von Ihnen selbst oder innerhalb einer Ressourcenklasse erstellt wurden) werden beim Verlassen des Bereichs zerstört.

Question

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:

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?




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




There are many excellent answers already, but let me give you one example:

I have an simple Item class:

 class Item
    {
    public: 
      std::string name;
      int weight;
      int price;
    };

I make a vector to hold a bunch of them.

std::vector<Item> inventory;

I create one million Item objects, and push them back onto the vector. I sort the vector by name, and then do a simple iterative binary search for a particular item name. I test the program, and it takes over 8 minutes to finish executing. Then I change my inventory vector like so:

std::vector<Item *> inventory;

...and create my million Item objects via new. The ONLY changes I make to my code are to use the pointers to Items, excepting a loop I add for memory cleanup at the end. That program runs in under 40 seconds, or better than a 10x speed increase. EDIT: The code is at http://pastebin.com/DK24SPeW With compiler optimizations it shows only a 3.4x increase on the machine I just tested it on, which is still considerable.




Well the main question is Why should I use a pointer rather than the object itself? And my answer, you should (almost) never use pointer instead of object, because C++ has references , it is safer then pointers and guarantees the same performance as pointers.

Another thing you mentioned in your question:

Object *myObject = new Object;

Wie funktioniert es? It creates pointer of Object type, allocates memory to fit one object and calls default constructor, sounds good, right? But actually it isn't so good, if you dynamically allocated memory (used keyword new ), you also have to free memory manually, that means in code you should have:

delete myObject;

This calls destructor and frees memory, looks easy, however in big projects may be difficult to detect if one thread freed memory or not, but for that purpose you can try shared pointers , these slightly decreases performance, but it is much easier to work with them.

And now some introduction is over and go back to question.

You can use pointers instead of objects to get better performance while transferring data between function.

Take a look, you have std::string (it is also object) and it contains really much data, for example big XML, now you need to parse it, but for that you have function void foo(...) which can be declarated in different ways:

  1. void foo(std::string xml); In this case you will copy all data from your variable to function stack, it takes some time, so your performance will be low.
  2. void foo(std::string* xml); In this case you will pass pointer to object, same speed as passing size_t variable, however this declaration has error prone, because you can pass NULL pointer or invalid pointer. Pointers usually used in C because it doesn't have references.
  3. void foo(std::string& xml); Here you pass reference, basically it is the same as passing pointer, but compiler does some stuff and you cannot pass invalid reference (actually it is possible to create situation with invalid reference, but it is tricking compiler).
  4. void foo(const std::string* xml); Here is the same as second, just pointer value cannot be changed.
  5. void foo(const std::string& xml); Here is the same as third, but object value cannot be changed.

What more I want to mention, you can use these 5 ways to pass data no matter which allocation way you have chosen (with new or regular ).

Another thing to mention, when you create object in regular way, you allocate memory in stack, but while you create it with new you allocate heap. It is much faster to allocate stack, but it is kind a small for really big arrays of data, so if you need big object you should use heap, because you may get , but usually this issue is solved using STL containers and remember std::string is also container, some guys forgot it :)




Let's say that you have class A that contain class B When you want to call some function of class B outside class A you will simply obtain a pointer to this class and you can do whatever you want and it will also change context of class B in your class A

But be careful with dynamic object




In areas where memory utilization is at its premium , pointers comes handy. For example consider a minimax algorithm, where thousands of nodes will be generated using recursive routine, and later use them to evaluate the next best move in game, ability to deallocate or reset (as in smart pointers) significantly reduces memory consumption. Whereas the non-pointer variable continues to occupy space till it's recursive call returns a value.




Aber ich kann nicht herausfinden, warum sollten wir es so benutzen?

Ich werde vergleichen, wie es im Funktionskörper funktioniert, wenn Sie Folgendes verwenden:

Object myObject;

Innerhalb der Funktion wird Ihr myObject zerstört, sobald diese Funktion zurückkehrt. Dies ist nützlich, wenn Sie Ihr Objekt nicht außerhalb Ihrer Funktion benötigen. Dieses Objekt wird auf den aktuellen Thread-Stack gesetzt.

Wenn Sie innerhalb des Funktionskörpers schreiben:

 Object *myObject = new Object;

Dann wird die Object-Klasseninstanz, auf die myObject zeigt, nicht zerstört, sobald die Funktion endet und die Zuweisung auf dem Heap erfolgt.

Nun, wenn Sie Java-Programmierer sind, dann ist das zweite Beispiel näher, wie die Objektzuordnung unter Java funktioniert. Diese Zeile: Object *myObject = new Object; entspricht java: Object myObject = new Object(); . Der Unterschied ist, dass unter Java myObject Garbage gesammelt wird, während unter C ++ es nicht freigegeben wird, müssen Sie irgendwo `delete myObject explizit aufrufen; ' Andernfalls werden Speicherlecks eingeführt.

Seit c ++ 11 können Sie sichere Wege für dynamische Zuweisungen verwenden: new Object , indem Sie Werte in shared_ptr / unique_ptr speichern.

std::shared_ptr<std::string> safe_str = make_shared<std::string>("make_shared");

// since c++14
std::unique_ptr<std::string> safe_str = make_unique<std::string>("make_shared"); 

Außerdem werden Objekte sehr oft in Containern wie map-s oder vector-s gespeichert, sie verwalten automatisch die Lebensdauer Ihrer Objekte.




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. Nichtsdestotrotz kann es keinen 100% igen Schutz gegen Speicherlecks bieten (ja, Java kann Speicher verlieren ). 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 Mittel, um damit umzugehen ( intelligente Zeigerfamilie, QObject in Qt und so weiter), aber keiner von ihnen kann in "Feuer und Vergessen" -Manövern wie GC verwendet werden: Sie sollten immer Gedächtnisbehandlung im Gedächtnis behalten. 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



A pointer directly references the memory location of an object. Java has nothing like this. Java has references that reference the location of object through hash tables. You cannot do anything like pointer arithmetic in Java with these references.

To answer your question, it's just your preference. I prefer using the Java-like syntax.




With pointers ,

  • can directly talk to the memory.

  • can prevent lot of memory leaks of a program by manipulating pointers.




Object *myObject = new Object;

Doing this will create a reference to an Object (on the heap) which has to be deleted explicitly to avoid memory leak .

Object myObject;

Doing this will create an object(myObject) of the automatic type (on the stack) that will be automatically deleted when the object(myObject) goes out of scope.






Related