new und realloc
-
groovemaster schrieb:
FreakyBKA schrieb:
irgendwie lest ihr teilweise nicht richtig oder habt schon wieder vergessen was die eigentliche fragestellung war. wenn ich das mit new() mache, dann sind wir doch wieder beim alten problem, falls ich das array nachträglich vergrößern will, weil ich dann kein realloc nutzen sollte.
Das war doch mehr oder weniger nur pseudo. Du kannst das natürlich auch mit malloc machen. Nur dann nicht vergessen, placement new zu verwenden, damit die Elemente korrekt initialisiert sind. Es ging einfach nur darum, den Container value-basiert zu gestalten, und nicht über diese Zeiger Indirektion. Zumindest bei Arrays halte ich das für sinnvoller.
Eigenlich sind 7 Seiten über das Thema ja genug
Aber trotzdem ist seine Frage ja nicht beantwortet und mich interessiert ehrlich gesagt auch, was dir so als Lösung vorschwebt. Kannst du vielleicht ein kurzes Beispiel posten, wie dann die Array-vergrößerungs-Funktion aussehen würde? 
-
mit realloc (Quelle wurde mit malloc/realloc/calloc angefordert)
template<typename T> T* resize(T* p, size_t new_size) { T* q = static_cast< T* >( realloc( p, new_size * sizeof *p ) ); if ( q == NULL ) throw bad_alloc(); // p wurde nicht gelöscht! return q; }klar, einfach aber mit Einschränkungen:
T muss ein POD sein und darf kein alignment haben, dass größer ist als das des in dieser Hinsicht striktesten Standardtyps (Problem also z.B. bei 128bit SSE Datentypen).mit placement new
template<typename T> T* resize(T* p, size_t old_size, size_t new_size) { char* qq = new char[ new_size * sizeof *p ]; // wir können kein new T[.] verwenden, denn das würde bei nicht-PODs den Defaultkonstruktor aufrufen T* q = reinterpret_cast< T* >( qq ); // aufpassen beim delete, dass wir einen char* Pointer löschen! // alternativ kann nat. auch eine andere Allokationsfunktion mit geeigneten alignmentgarantien verwendet werden size_t i = 0; try { for ( ; i < min( old_size, new_size ); ++i ) new( q + i ) T( p[ i ] ); } catch ( ... ) { for ( ; i != 0; ) q[ --i ].~T(); delete [] qq; throw; } for ( i = old_size; i != 0; } p[ --i ].~T(); delete [] reinterpret_cast< const char* >( p ); return q; }Nicht mehr auf PODs beschränkt, aber wegen der char*/T* problematik geringfügig unhandlich. Hinsichtlich alignments so problematisch wie die zugrundeliegende Allokationsfunktion (kein Problem mit new und Standardtypen).
Das ganze mit uninitialized_copy:
template<typename T> T* resize(T* p, size_t old_size, size_t new_size) { char* qq = new char[ new_size * sizeof *p ]; T* q = reinterpret_cast< T* >( qq ); try { uninitialized_copy( p, p + min( old_size, new_size ), q ); } catch ( ... ) { delete [] qq; throw; } for ( i = old_size; i != 0; } p[ --i ].~T(); delete [] reinterpret_cast< const char* >( p ); return q; }mit Allokator
template< typename T, template< typename > class A > T* resize(T* p, size_t old_size, size_t new_size) { A< T > alloc; T* q = alloc.allocate( new_size, p ); try { uninitialized_copy( p, p + min( old_size, new_size ), q ); } catch ( ... ) { alloc.deallocate( q ); throw; } for ( size_t i = old_size; i != 0; ) alloc.destroy( p + --i ); alloc.deallocate( p ); return q; }Das dürfte die leistungsfähigste Variante sein, keine Einschränkungen hinsichtlich PODs oder alignment (sofern entsprechende Allokatoren existieren), und die Allokationsstrategie kann flexibel optimiert werden.
P.S. old_size ist jeweils die Anzahl der initialisierten Elemente im ursprünglichen Array, nicht notwendigerweise dessen Größe
-
@camper:
entwickelst du selber compiler oder woher kommt dein extrem gutes fachwissen?
:xmas2:
-
ok, damit dürfte alles geklärt sein^^
Aber hab noch eine Frage zu dem Code:
new( q + i ) T( p[ i ] );Gibt man da mit q+i die Speicherstelle an, wo das Objekt erstellt werden soll?
-

was ist POD
was ist alignment
gehts auch noch komplizierter? :p
gehts auch so?template <class t> inline void carray<t>::set_size(int size) { if (size == _size) return; try { _data = (t**) realloc (_data, size * sizeof(t*)); if (size && !_data) throw memory_exception(0); _upper_bound = min(size - 1, _upper_bound); for(int i = _size; i < size; i++) _data[i] = 0; _size = size; } catch(cexception e) { _size = 0; _upper_bound = -1; cout << e; }; };cexception, memory_exception: fehlerklassen von mir
_data: array mit den zeigern
_size: größe des allozierten array
_upper_bound: größter zulässiger index (kann auch kleiner als _size-1 sein)wenn das so nicht geht, wüsste ich gern wo genau die probleme liegen bzw welchen einschränkungen diese version unterliegt, da sie bis jetzt funktioniert.
-
POD - keine Ahnung^^
Alignment - IMO wird der Speicher den du dir holst, auf eine gerade Bytezahl gestellt. D.h. wenn du dir 1 Byte holst (malloc/realloc/new), ist der Speicher dafür je nach System 4, 8, 16 Bytes groß oder so.
Das Problem bei deinem Code ist, dass, wenn der Speicher verkleinert wird, der Destruktor für die entfallenen Objekte nicht aufgerufen wird. Für mehr Objekte wird der Konstruktor für die neuen nicht aufgerufen. Und wenn die Objekte "verschoben" werden, weil realloc Speicher an einer anderen Stelle zurückgibt, kriegen die Objekte nix davon mit, dass sie woanders liegen, was zu Problemen führen kann.
Bei campers (abgefahrenem) Code werden size-Anzahl Objekte neu erzeugt, für die wird der Copy-Konstruktor mit Referenz auf das alte Objekt aufgerufen. Die alten werden dann dekonstrukturiert.
-
FreakyBKAs code enthält zusätzlich ein Speicherleck in:
_data = (t**) realloc (_data, size * sizeof(t*));wenn die Anforderung nicht erfüllt werden kann (realloc liefert 0 zurück). Denn der ursprüngliche Speicherblock wird dann (richtigerweise) nicht freigegeben. Der Destruktoraufruf ist kein Problem, denn es geht ja hier nur um Pointer.
-
das problem ist also, wenn realloc 0 zurückgibt hab ich keine zeiger mehr auf die objekte?
-
FreakyBKA schrieb:
das problem ist also, wenn realloc 0 zurückgibt hab ich keine zeiger mehr auf die objekte?
exakt.
-
FreakyBKA schrieb:
das problem ist also, wenn realloc 0 zurückgibt hab ich keine zeiger mehr auf die objekte?
exakt. Die Funktion sollte am besten nichts verändern, wenn sie nicht in der Lage ist, ihre Funktion auszuführen. Du veränderst aber den member _data bevor du weißt, ob nicht eine exception geworfen werden muss.
-
also wäre folgendes besser:
template <class t> inline void carray<t>::set_size(int size) { if (size == _size) return; try { t** tmp = (t**) realloc (_data, size * sizeof(t*)); if (size && !tmp) throw memory_exception(0); _data = tmp; for(int i = size; i < _size; i++) { delete _data[i]; _data[i] = 0; }; for(int i = _size; i < size; i++) _data[i] = 0; _upper_bound = min(size - 1, _upper_bound); _size = size; } catch(cexception e) { cout << e; }; };
-
FreakyBKA schrieb:
also wäre folgendes besser:
template <class t> inline void carray<t>::set_size(int size) { if (size == _size) return; try { t** tmp = (t**) realloc (_data, size * sizeof(t*)); if (size && !tmp) throw memory_exception(0); _data = tmp; for(int i = size; i < _size; i++) { delete _data[i]; _data[i] = 0; }; for(int i = _size; i < size; i++) _data[i] = 0; _upper_bound = min(size - 1, _upper_bound); _size = size; } catch(cexception e) { cout << e; }; };schon. wie kommst du eigentlich auf die Idee, per delete _data[i] daten zu löschen, wenn du schon realloc aufgerufen hast und _data[i] außerhalb deines Datenblocks liegt ?
Das mußt du vorher machen. Glücklicherweise schlägt realloc niemals fehl, wenn die angeforderte Größe nicht größer als der bereitsvorliegende Datenblock ist - du hast hier also kein Problem.
-
stimmt, hab ich übersehen werde ich gleich mal ändern.
-
was mache ich denn wenn ich einen typ der form
int[2], char*oder so habe, also quasi wieder dynamischer speicher.
der muss ja auch wieder ordnungsgemäß freigegeben werden.
-
Nein. Du musst nur Speicher wieder freigeben, den du mit new oder malloc angefordert hast.