Hallo zusammen
Ich bin noch relativ frisch mit C++ unterwegs und habe ein Problem, für
das mir die richtigen Stichworte für eine erfolgreiche Suche fehlen
(deswegen auch der unpräzise Titel).
Hier mein Minimalbeispiel:
1
#include<new>
2
#include<iostream>
3
4
classbase{
5
public:
6
virtual~base(){}
7
virtualcharGetSymbol()const=0;
8
};
9
10
classderivedA:publicbase{
11
public:
12
virtualcharGetSymbol()const{return'A';}
13
};
14
15
classderivedB:publicbase{
16
public:
17
virtualcharGetSymbol()const{return'B';}
18
};
19
20
intmain(void){
21
base*Var=newderivedA;
22
std::cout<<Var->GetSymbol()<<'\n';/* Ausgabe: A */
23
/* tu was */
24
deleteVar;
25
Var=newderivedB;
26
std::cout<<Var->GetSymbol()<<'\n';/* Ausgabe: B */
27
28
return0;
29
}
Das Beispiel funktioniert wie gewünscht, jedoch gefallen mir die new und
delete Operatoren nicht. Dies ist mir zu fehleranfällig und möchte diese
Operationen in eine Funktion oder ein Template verbannen, habe jedoch
keine Ahnung wie ich das tun soll. Ich nehme an, dass ich nicht der
erste mit diesem Problem bin und es eine elegante Lösung dafür gibt.
Vielen Dank für eure Hilfe!
Du kannst das delete loswerden, wenn du Smart Pointer benutzt (ein C++11
Feature), aber um das new kommst du nicht herum:
1
#include <memory>
2
3
[...]
4
5
int main(void){
6
std::shared_ptr<base> Var(new derivedA);
7
std::cout << Var->GetSymbol() << std::endl; /* Ausgabe: A */
8
/* tu was */
9
Var = std::shared_ptr<base>(new derivedB);
10
std::cout << Var->GetSymbol() << std::endl; /* Ausgabe: B */
11
return 0;
12
}
In der Zeile mit der Zuweisung wird der ursprüngliche Pointer
automatisch gelöscht.
Den Header <new> musst du übrigens nicht explizit einbinden.
Außerdem benutze bitte std::endl anstelle von '\n'.
Viele liebe Grüße,
Bjørn
Ermessensfrage.
Bei Debug Ausgaben wäre es schon gut, wenn Ausgaben auch tatsächlich
sofort geflushed werden und nicht in irgendeinem Cache rumlungern. Sonst
kann es passieren, dass mit dem Programmabsturz auch die letzten paar
Debug Ausgaben den Bach runtergehen und man mit Fehlersuche an der
falschen Stelle anfängt
std::endl ein Ausgabepuffer wird geflushed
\n nichts dergleichen passiert
Sonst gibt es keinen Unterschied zwischen den beiden.
https://cppkid.wordpress.com/2008/08/27/why-i-prefer-n-to-stdendl/
Karl Heinz schrieb:> Bei Debug Ausgaben wäre es schon gut, wenn Ausgaben auch tatsächlich> sofort geflushed werden und nicht in irgendeinem Cache rumlungern.
Genau deshalb schreibt man aber Debugausgaben ja nicht nach cout,
sondern cerr...
Trotzdem ist es eher empfehlenswert, nicht \n zu verwenden, sondern
std::endl, auch wenn es auf aktuellen Systemen aufs gleiche rausläuft
(ggf.abgesehen von der Pufferung).
Karl Heinz schrieb:> Bei Debug Ausgaben wäre es schon gut, wenn Ausgaben auch tatsächlich> sofort geflushed werden und nicht in irgendeinem Cache rumlungern.
Diagnoseausgaben gehören eigentlich nach stderr. Da in der Windows-Welt
cout sowieso nur zum Debuggen benutzt wird und niemand Pipes benutzt,
nimmt den Unterscheid keiner so richtig ernst.
Bjørn schrieb:> Du kannst das delete loswerden, wenn du Smart Pointer benutzt (ein C++11> Feature), aber um das new kommst du nicht herum:
Das sieht gut aus, daran hatte ich gar nicht gedacht. Wenn das new
bleibt, ist das in Ordnung. Habe nur keine Lust wegen einem vergessenen
delete ein Speicherleck zu produzieren.
Bjørn schrieb:> Außerdem benutze bitte std::endl anstelle von '\n'.
Komme aus der C-Ecke, deshalb bin ich mir das so gewohnt. Ausserdem
führe ich ein flush immer manuell aus, wenn es nötig ist. Bei kleinen
Ausgaben ist es auch egal, wenn nach jedem Zeichen ein flush ausgeführt
wird, bei einigen MB, die in eine Datei geschrieben werden, siehts
wieder anders aus.
Karl Heinz schrieb:> std::endl ein Ausgabepuffer wird geflushed> \n nichts dergleichen passiert>> Sonst gibt es keinen Unterschied zwischen den beiden.
Ist es nicht so dass es auch zusätzlich je nach Platform die dort
übliche Zeilenende-Sequenz (\r oder \r\n oder \n) sendet, je nach dem wo
es kompiliert wurde?
Das betrifft ja primär den MSDOS-Mist mit CR+LF statt LF, und da werden
sowohl bei \n als auch bei std::endl unter DOS/Windows das CR+LF
generiert (es sein denn, man macht eine Binärdatei und nimmt \n, dann
erhält man nur LF - aber da ist man selbst schuld).
Analog, wenn bei kranken Betrübssystemen nur ein CR als Zeileende
genommen wird.
nocheinGast schrieb:> Also ich schreib ja Debug-Ausgaben immer nach "clog" ;) .> Das new kannst du zumindest verstecken, wenn dus so machst:auto obj => std::make_shared<base>();
Dann aber
1
std::make_shared<derivedA/B>()
, weil man keine Objekte von base instanziieren kann. Natürlich kann man
das Ergebnis auch in einem base-Smartpointer speichern:
be stucki schrieb:> Dies ist mir zu fehleranfällig und möchte diese> Operationen in eine Funktion oder ein Template verbannen, habe jedoch> keine Ahnung wie ich das tun soll.
Kein Problem.
be stucki schrieb:> Das Beispiel funktioniert wie gewünscht, jedoch gefallen mir die new und> delete Operatoren nicht.
Die Idee mit den diversen shared-ptr wurde ja schon erwähnt (gibt
mehrere, je nachdem welchen C++-Standard man nimmt oder ggf. boost oder
anderes).
Die werden jeweils mit einem Zeiger initialisiert, den man aus einem new
erhält, letztlich spart man sich dadurch das delete.
Falls du das new auch noch loswerden willst, ist der übliche Weg, in die
Klasse eine factory-Methode einzubauen. Das ist eine statische Methode
(heißt häufig create()), in der das new versteckt ist und die dann den
Zeiger liefert.
In der Basisklasse wird es bereits definiert (bzw. in deinem Fall als
pure virtual, da es eine ABC ist) und in den abgeleiteten Klassen
überschrieben.
(Häufig paart man create() gleich mit einer nicht statischen Methode
clone(), die eine Kopie eines Objekts liefert und ebenfalls einen Zeiger
darauf liefert).
Anstatt;
1
...=newderivedA;
schreibt man dann:
1
...=derivedA::create();;
Damit hat man das new auch aus dem eigenen Code verbannt.
Zudem kann man den Zeiger dann in einem smart pointer speichern je nach
Belieben, das bleibt davon unberührt.
Das typedef gefällt mir, jedoch wollte ich die Tatsache, dass es ein
shared_ptr ist, nicht nicht unterschlagen. Evt. will ich auch mal
zusätzlich einen unigue_ptr verwenden. Etwas unschön empfinde ich, dass
man Zeiger auf const separat behandeln muss, sehe aber keinen Weg, wie
sich das ändern lassen könnte.
Geht auch ohne shared_ptr, wenn es ok ist, dass das Objekt zum Ende des
scopes freigegeben wird, in dem es angelegt wird. In anderen Worten: Der
Zeiger sollte nicht in Variablen gespeichert werden, die länger leben
als die Funktion läuft, in der du es anlegst.
1
intmain(void){
2
derivedAa;
3
base*Var=&a;
4
std::cout<<Var->GetSymbol()<<'\n';/* Ausgabe: A */
5
derivedBb;
6
Var=&b;
7
std::cout<<Var->GetSymbol()<<'\n';/* Ausgabe: B */
8
9
return0;
10
}
Nicht arg sinnvoll in diesem Beispiel - du könnest auch a und b direkt
verwenden, statt über Var. Ist aber z.B. sinnvoll wenn du so eine
Funktion aufrufen willst: