Forum: PC-Programmierung [C++] Abgeleitete Klasse umwandeln


von B. S. (bestucki)


Lesenswert?

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
class base{
5
  public:
6
    virtual ~base() {}
7
    virtual char GetSymbol() const = 0;
8
};
9
10
class derivedA : public base{
11
  public:
12
    virtual char GetSymbol() const {return 'A';}
13
};
14
15
class derivedB : public base{
16
  public:
17
    virtual char GetSymbol() const {return 'B';}
18
};
19
20
int main(void){
21
  base * Var = new derivedA;
22
  std::cout << Var->GetSymbol() << '\n'; /* Ausgabe: A */
23
  /* tu was */
24
  delete Var;
25
  Var = new derivedB;
26
  std::cout << Var->GetSymbol() << '\n'; /* Ausgabe: B */
27
  
28
  return 0;
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!

: Bearbeitet durch User
von Bjørn (Gast)


Lesenswert?

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

von Tom (Gast)


Lesenswert?

Bjørn schrieb:
> benutze bitte std::endl anstelle von '\n'.

Warum?

von Karl H. (kbuchegg)


Lesenswert?

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/

: Bearbeitet durch User
von Klaus W. (mfgkw)


Lesenswert?

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).

: Bearbeitet durch User
von Tom (Gast)


Lesenswert?

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.

von B. S. (bestucki)


Lesenswert?

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.

von nocheinGast (Gast)


Lesenswert?

Also ich schreib ja Debug-Ausgaben immer nach "clog" ;) .
Das new kannst du zumindest verstecken, wenn dus so machst:
1
auto obj = std::make_shared<base>();

von Bernd K. (prof7bit)


Lesenswert?

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?

: Bearbeitet durch User
von Klaus W. (mfgkw)


Lesenswert?

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.

von jgdo (Gast)


Lesenswert?

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:
1
std::shared_ptr<base> var = std::make_shared<derivedA>(...)
.

Wenn man viel mit Smartpointern arbeitet, lohnt es sich auch, 
entsprechende Typedefs in den Klassen zu machen
1
class base{
2
  public:
3
    typedef std::shared_ptr<base> Ptr;
4
    typedef std::shared_ptr<const base> ConstPtr;
5
  ...
6
};
7
8
typedef basePtr base::Ptr;
9
typedef baseConstPtr base::ConstPtr;
10
11
class derivedA : public base{
12
  public:
13
    typedef std::shared_ptr<derivedA> Ptr;
14
    typedef std::shared_ptr<const derivedA> ConstPtr;
15
  ...
16
};
17
18
19
typedef derivedAPtr derivedA::Ptr;
20
typedef derivedAConstPtr derivedA::ConstPtr;

um dann später
1
basePtr var; bzw. base::Ptr var;
 anstatt
1
std::shared_ptr<base> var
 schreiben zu können.

von Oliver S. (oliverso)


Lesenswert?

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.
1
int main(void)
2
{
3
  std::cout << "A" << std::endl;/* Ausgabe: A */
4
  /* tu was */
5
  std::cout << "B" << std::endl /* Ausgabe: B */
6
  
7
  return 0;
8
}

Supersicher und supereinfach ;)

Oliver

von Klaus W. (mfgkw)


Lesenswert?

Oliver S. schrieb:
> Supersicher und supereinfach ;)

... und Super am Thema vorbei :-)

von Klaus W. (mfgkw)


Lesenswert?

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
  ... = new derivedA;
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.

von B. S. (bestucki)


Lesenswert?

Vielen Dank für die Hilfe, ihr habt mir sehr geholfen.

Ich habe die Sache nun so gelöst:
1
#include <iostream>
2
#include <memory>
3
4
class base{
5
  public:
6
    typedef std::shared_ptr<base> shared_ptr;
7
    typedef std::shared_ptr<const base> const_shared_ptr;
8
    
9
    virtual ~base() {}
10
    virtual char GetSymbol() const = 0;
11
};
12
13
class derivedA : public base{
14
  public:
15
    static shared_ptr CreateShared() {return shared_ptr(new derivedA{});}
16
    static const_shared_ptr CreateConstShared(){return const_shared_ptr(new const derivedA{});}
17
    
18
    virtual char GetSymbol() const {return 'A';}
19
};
20
21
class derivedB : public base{
22
  public:
23
    static shared_ptr CreateShared() {return shared_ptr(new derivedB{});}
24
    static const_shared_ptr CreateConstShared(){return const_shared_ptr(new const derivedB{});}
25
    
26
    virtual char GetSymbol() const {return 'B';}
27
};

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.

: Bearbeitet durch User
von Sebastian (Gast)


Lesenswert?

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
int main(void){
2
  derivedA a;
3
  base * Var = &a;
4
  std::cout << Var->GetSymbol() << '\n'; /* Ausgabe: A */
5
  derivedB b;
6
  Var = &b;
7
  std::cout << Var->GetSymbol() << '\n'; /* Ausgabe: B */
8
  
9
  return 0;
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:
1
void zeigDenBuchstaben(const base* Var)
2
{
3
    std::cout << Var->GetSymbol() << '\n';
4
}

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.