[C++] Abgeleitete Klasse umwandeln

OP #3964903
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!
Gast #3964937
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
#3964950
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/
#3964966
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).
Gast #3964975
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.
OP #3964990
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.
#3965882
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.
Gast #3967283
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.
#3967945
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
#3968015
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.
OP #3968611
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.
Gast #3986302
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
}

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren