Willkommen ~Gast!
Registrieren || Einloggen || Hilfe/FAQ || Staff
Probleme mit der Registrierung im Forum? Melde dich unter registerEin Bild.
Autor Beitrag
000
11.08.2010, 15:48
ElChupkapres



hi,

ich habe eine Frage bzw. ein Problem bezüglich des löschens von dynamisch erstellten Arrays.

Ich hab eine Klasse C, der ein Array und die größe des Arrays übergeben wird. Der Konstruktor erstellt ein dynamisches Array mit der entsprechenden Größe und kopiert die Werte des Arrays. (Hab den Code sehr minimal gehalten wegen der Übersicht)

Quellcode:class C
{
public:
    C(double array[], int size);
    ~C();
    int getSize() const;
    double getValue(int index) const;

protected:
    double *m_array;
    int m_size;
    
};


C::C(double array[], int size) : m_size(m)
{
    m_array = new double[m_size];
    for (int i = 0; i < m_size; i++) {
        m_array[i] = array[i];
    }
};

C::~C()
{
    // Bewußt auskommentiert, siehe unten
    //delete[] m_array;
    //m_array = 0;
};

int
C::getSize() const
{
    return m_size;
};

double
C::getValue(int index) const
{
    return m_array[index];
};
Des weiteren hab ich eine Klasse B, in der Instanzen von C erstellt werden und in einem std::vector gespeichert werden.

Quellcode:class B
{
public:
    B();
    ~B();
    std::vector<C> get_C_list() const;
    void run();

protected:
    std::vector<C> m_C_list;

void
B::run()
{
    m_C_list.clear();
    for (int i = 0; i < xyz; i++) {
        
        double array[128];
        C instanzC(array, 128);
        m_C_list.push_back(instanzC);
    };
};

std::vector<C>
B::get_C_list() const
{
    return m_C_list;
};
In der Main wird jetzt eine Instanz der Klasse B erstellt und die Methode aufgerufen, in der B den Vector mit Instanzen von C füllt.

Quellcode://main.cpp

B instanzB;
B.run();
std::cout << B.get_C_list().size() << std::endl;
std::cout << B.get_C_list().[0].getValue(0) << std::endl;
Das klappt soweit auch alles. Wenn ich nun nocheinmal die Methode B.run() aufrufe, wird die Liste mit den C-Instanzen geleert. Allerdings wird, wenn ich mich nicht irre, der Speicherplatz der Arrays nicht wieder freigegeben. Wenn ich in C einen Destruktor erstelle, der das Array mit delete[] wieder löscht, kommt es zu einem Fehler wenn ich in der main noch auf die Instanz von C zugreifen will.

Daher die Fragen:
Wird der Destruktor wenn ich dort ein delete[] einfüge zu einem anderen Zeitpunkt aufgerufen? Da ich mit dem Standard-Destruktor später in main z.B. noch auf getSize() von C zugreifen kann, mit einem eigenen aber nicht.

Wie gebe ich jetzt am besten den Speicherplatz wieder frei, wenn ich das nicht über den Destruktor mache. Wenn ich es mit Destruktor mache kann ich später nicht mehr auf meine Instanzen von C zugreifen. Ich könnte zwar eine delete Methode in der Klasse C implementieren, aber dann müsste man die jedensmal aufrufen bevor der Vector gelert wird, das kann man leicht vergessen.

Bei den Beispielen die man im Netz findet, wird auf ein Objekt mit dynamischen Array immer nur innerhalb des Scopes auf das Objekt zugegriffen, daher habe ich leider bisher keine passende Lösung gefunden.

Hoffe ihr könnt mir ein paar Tipps geben.

--

zum Seitenanfang zum Seitenende Profil || Suche
001
11.08.2010, 16:32
Bluthund



Zitat:
Wenn ich in C einen Destruktor erstelle, der das Array mit delete[] wieder löscht, kommt es zu einem Fehler wenn ich in der main noch auf die Instanz von C zugreifen will.
Du hantierst in Klasse C manuell mit Speicher herum aber hast weder den Kopierkonstruktor noch operator=() ueberschrieben.
Das bedeutet, dass dein Pointer auf den geholten Speicher bei einem Kopiervorgang der Klasseninstanzen (was u.a. beim Einfuegen von Objekten in eine STL-Container-Klasse geschieht) einfach 1:1 in die neue Instanz kopiert wird. Du hast also nach dem Kopieren zwei Instanzen, deren m_array-Pointer beide auf das selbe Array im Speicher zeigen. Wenn du dann delete[] auf diesem Array in einer der beiden Instanzen aufrufst zeigt der Pointer der anderen nach wie vor an diese Stelle. Wenn du dann versuchst ueber diesen Pointer zu lesen oder zu schreiben kracht es verstaendlicherweise.

Ausserdem:
Das Standardzugriffslevel in deinen Klassen fuer Member, die nicht von aussen erreichbar sein sollen, sollte private sein und nicht protected. Protected solltest du nur in Ausnahmefaellen benutzen, wo ein Zugriff aus einer abgeleiteten Klasse unbedingt noetig ist.

Warum arbeitest du in Klasse C ueberhaupt mit C-Style Arrays? Nimm einen vector<double>. Dann brauchst du dich auch nicht darum kuemmern, wie das Ding zu zerstoeren ist.

Du brauchst nach einem delete im Desktruktor auch den Pointer nicht 0 zu setzen, da der Pointer nach dem Austritt aus der Funktion eh verschwindet wenn das Objekt abgebaut wird.

--

The C language combines all the power of assembly language with all the ease-of-use of assembly language.
"humorig is n blödwort :>" by -CarniGGeLjumpR-


Dieser Beitrag wurde am 11.08.2010 um 16:41 von Bluthund bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
002
11.08.2010, 17:20
Kriz



Alle Vaterklassen sollten ihre Members grundsätzlich als protected deklarieren, damit Kindklassen direkt darauf zugreifen können und nicht umständlich über die öffentlichen Methoden der Vaterklasse. So wie Bluthund das beschrieben hat, machen das nur Noobs (wobei Bluthund keinesfall ein solcher ist). Private wird nur das deklariert, was die Vaterklasse angeht und sonst niemanden.

Wer allerdings eh nicht ableitet, der sollte von vornherein die Members private deklarieren, da hat Bluthund recht.

Und wer ganz qualvoll agieren möchte, der nutze protected/private Ableitungen...

@ElChupkapres: Bei Zugriffen auf Standard-Templates wie vector<> solltest du dir langsam mal den Zugriff per Iteratoren aneignen. for-Schleifen sind in diesem Fall ausnahmsweise sehr unflexibel gegenüber Iteratoren.

--

K:R-I)Z++
"CSS ist cascading style sheets. Und nicht so'n Ranzspiel." - dp
In memory of Voice († 2005/03/30)

zum Seitenanfang zum Seitenende Profil || Suche
003
11.08.2010, 18:24
Bluthund



Zitat:
Alle Vaterklassen sollten ihre Members grundsätzlich als protected deklarieren, damit Kindklassen direkt darauf zugreifen können und nicht umständlich über die öffentlichen Methoden der Vaterklasse.
Die einen nennen es umstaendlich und die anderen gute Kapselung (und die sollte nicht nur nach aussen sondern auch innerhalb einer OO-Architektur herrschen).

Eine Kindklasse erbt sowohl Verhalten als auch Zustand von ihren Eltern. Wenn du den Zustand aber prinzipiell fahrlaessig an jedes Kind freigibst, ist es nur eine Frage der Zeit bis irgendwann mal eine Kindklassenimplementierung irgendeine Invariante einer ihrer Elternklassen verletzt, weil sie uneingeschraenkten Zugriff auf deren Zustand hatte, und damit das Verhalten der Implementierung der Elternklasse (und damit auch das der Kindklasse) voellig unvorhersehbar wird. Und das gilt bei Weitem nicht nur fuer Membervariablen sondern auch fuer non-const Memberfunktionen.
Nutzt man aber prinzipiell erst einmal private, kann man den Schutz immernoch lockern indem entweder auf protected/public-Access umsteigt oder eine Memberfunktion anbietet, die protected/public ist, um einen Zugriff innerhalb definierter Parameter zu erlauben.
Ich zitiere an dieser Stelle mal Scott Meyers: "Good interfaces are easy to use correctly and hard to use incorrectly." Und eine Vererbung bildet auch ein Interface zwischen den involvierten Klassen.

Wenn es keine Invarianten gibt, die eine bestimmtes Basisklassenmember betreffen, spricht natuerlich nichts dagegen sie protected zu deklarieren, um einen einfachen Zugriff zu ermoeglichen. Wobei man sich hier natuerlich Gedanken machen sollte, ob das im Sinne der Konsistenz des Klasseninterfaces ist.
Aber grundsaetzlich in einer Basisklasse alles protected zu deklarieren hat am Ende der Fahnenstange innerhalb der Vererbungshierarchie den selben Effekt wie eine Klasse, die alle ihre Member public zur Verfuegung stellt.

Neulinge deklarieren wohl auch eher alles public als sich ueberhaupt um Zugriffslevel zu scheren.

--

The C language combines all the power of assembly language with all the ease-of-use of assembly language.
"humorig is n blödwort :>" by -CarniGGeLjumpR-


Dieser Beitrag wurde am 11.08.2010 um 18:33 von Bluthund bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
004
11.08.2010, 22:23
Kriz



Sei mir nicht böse, aber du brauchst mir keine Abhandlung über C++ Vererbung zu halten. Was ich gesagt habe, ist einfach nur jahrelange Praxiserfahrung. In C++ dem Risiko eines Zugriffsmißbrauchs von Kindklassen nur durch konsequentes "privatisieren" der Elternklasse entgegenzutreten ist ein schlechter Ansatz, wenn es beispielsweise um Zugriffsgeschwindigkeit geht. Haben Kindklassen nur per geerbten (virtuellen) Methoden Zugriff auf Elternmembers, dann reduzieren sich die Geschwindigkeitsvorteile gegenüber einem Direktzugriff dramatisch. Ganz besonders sieht man sowas bei (selbstgestrickten) Templates, die mit fetten Matrizen, Vektoren oder Hashtables auf Hardcorelevel arbeiten. Wer hier auf deine Weise arbeitet, der verschenkt bare Kapazitäten und letztendlich teure Zeit.

Wie gesagt... Einem Drittcoder/Kunden eine Lib anzudrehen, die ebenso auf deine Weise aufgebaut ist, macht dich mit Garantie nicht zu seinem Freund! Wer ableiten muß/darf von vererbungsfähigen Klassen, der will auch direkten Zugriff auf vererbungsrelevante Members und Methoden der Elternklasse haben.

EDIT:

Und niemand sollte sich in C++ etwas auf "private" einbilden. Das Teil läßt sich in Nullkommanix per C aushebeln ;-)

--

K:R-I)Z++
"CSS ist cascading style sheets. Und nicht so'n Ranzspiel." - dp
In memory of Voice († 2005/03/30)


Dieser Beitrag wurde am 11.08.2010 um 22:25 von Kriz bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
005
12.08.2010, 01:42
Bluthund



Zitat:
wenn es beispielsweise um Zugriffsgeschwindigkeit geht.
Dafuer bietet C++ Inlining fuer hinreichend simple Funktionen und ausserdem hat man erst ein Performanceproblem wenn man es auch messen kann. Ich wage zu bezweifeln, dass es einen Menschen auf diesem Planeten gibt, der mit den Optimierungen, die ein guter C++-Compiler an den Tag legt, vom ersten Moment der Implementierung sagen kann wie schnell oder langsam sein Programm am Ende des Tages laufen wird und Leute, die dann trotzdem von Tag Eins an meinen saemtliche Ecken ihres Programms auf Performance zu trimmen, wandeln auf dem dunklen Pfad der Premature Optimization.
Zitat:
Haben Kindklassen nur per geerbten (virtuellen) Methoden Zugriff auf Elternmembers, dann reduzieren sich die Geschwindigkeitsvorteile gegenüber einem Direktzugriff dramatisch.
Voellig korrekt, ein Zugriff durch die vtable kostet (auch wenn diese Kosten wahrscheinlich nur in performanzkritischen Applikationen ueberhaupt eine Rolle spielen und ansonsten vom praktischen Nutzen aufgewogen werden).
Jetzt wuerde ich aber auch gern mal wissen welche Umstaende ueberhaupt einen virtuellen Mutator als Zugangspunkt fuer eine Membervariable der Basisklasse semantisch sinnvoll machen sollen? (Die Frage haette ich wirklich gern beantwortet)
Zitat:
Wer ableiten muß/darf von vererbungsfähigen Klassen, der will auch direkten Zugriff auf vererbungsrelevante Members und Methoden der Elternklasse haben.
Soll er ja auch haben, aber eben nur ueber ein klar definiertes Interface (mit dem beide Seiten arbeiten koennen) und nicht mit einer Kernbohrung mitten durch saemtliche Klassen der Vererbungshierarchie. Denn spaetestens wenn du die Implementierung der Basisklasse veraenderst, wird bei letzterem Fall sogar ziemlich sicher der Kunde an dich herantreten und fragen warum er denn jetzt mit der teuer bezahlten neuen Version auch noch $x Mann-Stunden in die Anpassung seines Codes stecken muss.
Zitat:
Und niemand sollte sich in C++ etwas auf "private" einbilden. Das Teil läßt sich in Nullkommanix per C aushebeln ;-)
Es geht nicht darum mutwilligen sondern versehentlichen Missbrauch zu vermeiden. Denn Menschen machen Fehler und Software-Systeme werden auch eher komplexer als einfacher.
Wenn ich ein "Betreten verboten"-Schild am Zaun aufstelle und trotzdem jemand ueber den Zaun steigt, dann darf sich dieser Mensch imho auch nicht wundern wenn er auf den Hund des Hauses trifft und das Grundstueck mit einem Koerperteil weniger verlaesst (oder ins Mun-Verbrauchschiessen der oertlichen Kaserne laeuft -- ich konnte mich nicht entscheiden welche Metapher den Punkt besser rueber bringt).
Denn die Forcierung von Zugriffsleveln oder auch const-Correctness durch den Compiler sind eben dafuer da dem Programmierer zu helfen (wenn sie denn mit Sinn und Verstand eingesetzt werden) und nicht um ihn zu gaengeln. Und wer meint diese Hilfen umgehen zu muessen und damit u.U. Code ausserhalb seiner Parameter betreibt, der braucht sich auch nicht wundern wenn bei dessen Ausfuehrung dann ploetzlich sein Haus in Flammen aufgeht oder die automatisierte Boersensoftware ploetzlich nur noch wie wild kauft statt zu verkaufen.

Ich verstehe vollkommen, dass du hier aus deiner eigenen Erfahrung sprichst und dementsprechende Empfehlungen machst. Aber Fakt ist auch, dass die Realitaet der Software-Entwicklung besonders in so maechtigen Sprachen wie C++ viele Dinge zugelassen hat, die schlicht und ergreifend fuer den Allerwertesten sind.
In dieser Richtung hat sich in der C++-Entwickler-Community in den letzten Jahren aber viel getan und es wurden Idiome, Empfehlungen und selbstauferlegte Restriktionen erarbeitet, um solche Sachen direkt im Keim zu ersticken und auch ein Stueck weit C++ zu "modernisieren". Und private-Zugriff protected vorzuziehen, wo moeglich (was wohl einen Grossteil der Faelle ausmacht), verhindert nun einmal sehr effektiv Integritaetsverletzungen und bewahrt einen vor voellig kaputtgekoppelten Hierarchien, bei denen man mit jeder Veraenderung Angst haben muss, dass irgendwo "down the road" eine Funktion der Eltern ausserhalb ihrer Anforderungen betrieben wird oder die Haelfte der erbenden Klassen (inhouse oder beim Kunden) mit seitenweise Compilerfehlern den Dienst quittieren.
Gerade im Namen der Performance wurden sicherlich auch genuegend Verbrechen begangen, die eine Fortentwicklung (im Sinne von echten Neuerungen oder Umgestaltungen) einiger Software voellig unmoeglich gemacht haben.

Ich hatte bereits das "Vergnuegen" Maintenance an einer mit "Just get it done"-Mentalitaet entstandenen OO-Architektur zu leisten und ich kann dir sagen, dass ich soweit moeglich meinen Nachfolgern nicht so etwas hinterlassen moechte.

Abschliessend noch:

Zitat:
Wer hier auf deine Weise arbeitet, der verschenkt bare Kapazitäten und letztendlich teure Zeit.
Jo, Zeit ist Geld. Aber Zeit hat auch einen relativen Wert. Denn Mann-Stunden sind zumindest in der westlichen Welt teurer als Hardware. Und die Vergangenheit hat gezeigt, dass Software immer laenger lebt als man denkt und viel Zeit und damit Geld fuer deren Wartung ausgegeben wird.
Zeit von der ein Teil von vielen dieser hellen Koepfe ganz anderen Problemen haette gewidmet werden koennen wenn man von Anfang an etwas mehr Acht gegeben haette.

--

The C language combines all the power of assembly language with all the ease-of-use of assembly language.
"humorig is n blödwort :>" by -CarniGGeLjumpR-

zum Seitenanfang zum Seitenende Profil || Suche
006
12.08.2010, 13:34
Kriz



Du hättest Philosoph werden sollen :)

Ansonsten lese ich viel Hysterie aus deiner Antwort raus, von bösen Absichten, veralteten Konzepten und schwachen Dogmata. Kann ich alles nicht so bestätigen, was aber nicht heißen soll, daß es auch Firmen und Leute gibt, die genauso nach den Vorstellungen handeln und arbeiten, die du hier aufgestellt hast. Aber darüber zu diskutieren bringt nichts, denn daß ist wie immer eine Geschmackssache.

Die präventive Vollkapselung gegenüber Kindklassen ohne sinnvollen Einsatz von protected Elementen ist meiner Meinung und Erfahrung nach vollkommener Schwachsinn. Wie gesagt: Geschmackssache, denn keines deiner Argumente gegen diese Technik hat mich überzeugt. Nicht mal ansatzweise.

Drum lassen wir es gutsein, sonst wird das eh wieder nur ein fruchtloser Diskussionsthread. Merci :)

--

K:R-I)Z++
"CSS ist cascading style sheets. Und nicht so'n Ranzspiel." - dp
In memory of Voice († 2005/03/30)

zum Seitenanfang zum Seitenende Profil || Suche
007
12.08.2010, 13:54
ElChupkapres



Erst mal vielen Dank ihr beiden. Jetzt da man es weiß macht es natürlich Sinn! Ich hab den Kopierkonstruktor und den operator=() überschrieben und es funktioniert. Trotzdem würde ich euch bitten noch mal schnell einen Blick drauf zu werfen:

Quellcode:C::C(const C &copyThis)
{
    m_size = copyThis.m_size;
    m_array = new double[m_size];
    
    for (int i = 0; i < m_size; i++) {
        m_array[i] = copyThis.m_array[i];
    }
}

C
&C::operator=(const C &copyThis)
{
    delete[] m_array;

    m_size = copyThis.m_size;
    m_array = new double[m_size];

    for (int i = 0; i < m_size; i++) {
        m_array[i] = copyThis.m_array[i];
    }
    return *this;
};
Zusätzlich wäre ich noch sehr dankbar wenn ihr mir ein paar weitere Fragen beantworten könntet:

1. Ist das delete[] m_array beim Überladen des Operators notwendig?

2. m_size = copyThis.m_size; Ist das hier ein Sonderfall das ich einfach auf m_size von copyThis zugreifen kann? Normalerweise sollte doch der Zugriff nicht möglich sein weil es private ist oder irre ich mich da?

3. Gibt es eine effizientere Möglichkeit das Array zu kopieren evtl. mit memcpy?

Bleibt noch zu sagen:

@private/protected: Bei Klasse C war es ein Schreibfehler, sollte private sein. Bei Klasse B ist es protected weil B Vaterklasse ist. Btw: Interessante Diskussion die ihr führt :)

@Iteratoren: Ja das wollte ich mir eigentlich schon lange näher angeguckt haben. Aber irgendwie hab ich bis jetzt den Vorteil noch nicht so richtig erkannt und in diesem Fall ist es mit einer for-Schleife so schön einfach...

--

zum Seitenanfang zum Seitenende Profil || Suche
008
12.08.2010, 14:39
Bluthund



1. Ja, sonst leakst du den Speicher von m_array da du 2 Zeilen spaeter ja den Pointer per new ueberschreibst.

2. Du befindest dich innerhalb der selben Klasse womit du vollen Zugriff auf jede der von ihr deklarierten Member hast. public, protected und private wirken nur auf Zugriffe von ausserhalb der Klasse.

3. Du kannst es mit memcpy versuchen. Das kann unter Umstaenden effizienter sein, wenn deine Implementierung groessere (aber trotzdem sinnvoll dimensionierte) Bloecke kopiert als du es mit deiner for-Schleife tust. Aber wie schon oben gesagt kann man Laufzeit-Effizienz nur messen und nicht schaetzen.
Wenn du mit Effizienz "weniger Code fuer aber mehr Effekt" meinst, kannst du dir std::copy anschauen (aus <algorithm>)
Verwendung wuerde dann so aussehen:
Quellcode:std::copy(copyThis, copyThis + m_size, m_array); Intern tut das aber nix anderes als einfach durch vom ersten Iterator bis zum zweiten zu loopen und dabei alles in den dritten zu schreiben.

edit:
@Kriz: Ok, das muss halt jeder fuer sich entscheiden wie er programmieren moechte. Aber ich haette trotzdem noch gern ein Beispiel fuer einen sinnigen virtuellen Mutator gesehen.

--

The C language combines all the power of assembly language with all the ease-of-use of assembly language.
"humorig is n blödwort :>" by -CarniGGeLjumpR-


Dieser Beitrag wurde am 12.08.2010 um 15:11 von Bluthund bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
009
19.08.2010, 15:48
ElChupkapres



Also noch mal vielen Dank, damit markiere ich das Thema mal als gelöst.

--

zum Seitenanfang zum Seitenende Profil || Suche