Jag kämpar med den här frågan som jag kan tycka komma runt. När jag definierar en vektorklass verkar jag för att få problem när du raderar den tilldelade pekaren.
Konstigt nog, detta händer bara efter att jag ”läst” minnet. Vad jag menar med detta är att om jag skjuter tillbaka en serie värden till vektorn verkar de stanna kvar. Men när jag har läst värdena verkar datapekaren bli ogiltig av något slag så jag kraschar när jag försöker placera den.
Kod:
#ifndef VECTOR_H #define VECTOR_H #include <string.h> /* memcpy */ #define min(a,b) (a < b) ? a : b template <typename T> class vector { public: vector() : __capacity(0), __count(0), __data(0) {} //empty constructor vector(T obj, int count = 1) { if (this->__data) { // free data ptr if used delete this->__data; } this->__data = new T[count]; this->__count = count; this->__capacity = count; for (int i = 0; i < count; i++) //populate array with given object this->__data[i] = obj; } ~vector() { if (this->__data) { // free data ptr if used delete [] this->__data; } this->__count = this->__capacity = 0; } T const & operator[] (unsigned int idx) const { if (idx < this->__count) { return this->__data[idx]; } else { // throw exception or handle error to be implemented } } T& operator[] (unsigned int idx) { if (idx < this->__count) { return this->__data[idx]; } else { // throw exception or handle error to be implemented } } void resize_to_fit() { resize(this->__count); } T& pop_back(){ return this->__data[--this->__count]; } void push_back(T obj) { if (this->__capacity == this->__count) { resize(this->__capacity + 1); } this->__data[this->__count++] = obj; } bool isempty() { return !this->__data || !this->capacity || !this->size; } void clear() { this->~vector(); } T* data() { return this->__data; } int size() { return this->__count; } int capacity() { return this->__capacity; } private: void resize(int capacity) { if (this->__data == nullptr) { this->__data = new T[capacity]; this->__count = 0; this->__capacity = capacity; } else if (capacity != this->__capacity) { //else do nothing T* data = new T[capacity]; this->__count = min(this->__count, capacity); this->__capacity = capacity; memcpy(data, this->__data, sizeof(T) * this->__count); delete this->__data; //program crashes here, but the pointer is already broken... this->__data = data; } } protected: int __capacity; int __count; T* __data; }; #endif//VECTOR_H
Jag använde den här koden i min Arduino, inte säker på om den hjälper
void print_vec(vector<int> v){ Serial.println("Printing new vec!"); for( int i = 0; i < v.size(); i++){ Serial.println(v[i]); } } void setup() { // put your setup code here, to run once: Serial.begin(9600); // Open serial connection to report values to host while(!Serial); //Waiting for serial to open vector<int> i = vector<int>(); i.push_back(10); i.push_back(2); print_vec(i); //prints 10 and 2, perfect so far i.push_back(3); while(true){ print_vec(i); i.pop_back(); delay(2000); } } void loop() { // put your main code here, to run repeatedly: }
Denna kod matar ut: Utskrift av ny vec! 10 2 Skriva ut ny vec! 0 2 3 Skriva ut ny vec! 0 2 Utskrift av ny vec! 0 (…)
Vad kan orsaka detta? Jag är stenhuggna ett tag nu, alla insikter om hur man löser detta uppskattas. Tack!
Kommentarer
- I slutet av vad du säg att kodutmatningarna är ”(…)” en del av utgången som du ser eller inte? Om den inte är ’ t, hur skiljer sig utdata från vad du förväntade dig att se? Om det finns fyra objekt i en vektor, ska fyra
i.pop_back()
samtal tömma vektorn. -
delete this->__data; //program crashes here ...
ska användadelete[]
rätt? Det finns inte heller någon plattformskod i din klass så du kan felsöka på pc där det finns mycket bättre felsökningsverktyg tillgängliga.
Svar
Du hade många problem med din kod så det är svårt att veta var du ska börja. 🙂 Visst valgrind rapporterade ett fel efter att du skrivit ut vektorn. Anledningen till det är enkel. Du skickade en kopia av klassen till print_vec
vilket innebar att förstöraren fick anropas när print_vec
avslutades och därmed omlokaliserar minnet. Du borde ha haft en kopikonstruktör . Utan det gör kompilatorn en bitvis kopia av klassen, vilket innebär att du nu har två objekt som delar samma tilldelade minne.
En snabb och smutsig fix är att ringa print_vec
med referens:
void print_vec(vector<int> & v){
Men det gör att felet lurar för framtiden.
Jag implementerade kopikonstruktören i mitt exempel nedan, men att ringa print_vec
som referens sparar dock klassen som måste kopieras, vilket minskar antalet new/delete
som du gör, vilket möjligen minskar minnesfragmentering.
Som John Burger sa: ring inte förstöraren själv! Du kan inte göra det. Om du vill göra samma sak i förstöraren och clear
-funktionen, får bara förstöraren att ringa clear()
.
Användningen av ledande dubbelstreck i variabler strider mot C ++ – standarden. Gör det inte. Använd en efterföljande understrykning om du vill ange en medlemsvariabel. Förlora alla this->
referenser. De förstör bara saker och ting.
Eftersom du tilldelar en matris bör du använda delete []
– du gjorde det på ett ställe men inte på det andra.
Varför gör det här? Det är en onödig uppgift:
vector<int> i = vector<int>();
Gör bara:
vector<int> i;
Om du kommer att tilldela klassen av någon anledning bör du också implementera en operator=
eller så får du samma problem som du hade med kopikonstruktören. (Se mitt exempel).
Här testar du funktioner (kapacitet och storlek) utan att anropa dem:
bool isempty() { return !this->__data || !this->capacity || !this->size; }
Du kan inte göra det här :
T const & operator[] (unsigned int idx) const { if (idx < this->__count) { return this->__data[idx]; } else { // throw exception or handle error to be implemented } }
Om du tar ”annars” -vägen returnerar funktionen inget värde (du får en kompilatorvarning om du aktiverar varningar).
Mitt omarbetade exempel med mina förslag i det. Den kompileras utan varningar eller fel och körs utan att krascha (på en PC):
// g++ -std=c++11 -g3 test.cpp -o test // OR: clang -std=c++11 -g3 -Wall test.cpp -lstdc++ -o test // // valgrind --leak-check=yes ./test #include <stdio.h> // printf #include <string.h> /* memcpy */ #define min(a,b) (a < b) ? a : b template <typename T> class vector { public: //empty constructor vector() : capacity_(0), count_(0), data_(nullptr) {} // copy constructor vector (const vector & rhs) : capacity_(0), count_(0), data_(nullptr) { data_ = new T [rhs.capacity_]; capacity_ = rhs.capacity_; count_ = rhs.count_; memcpy (data_, rhs.data_, sizeof (T) * count_); } // end of copy constructor // operator= vector & operator= (const vector & rhs) { // gracefully handle self-assignment (eg. a = a;) if (this == &rhs ) return *this; data_ = new T [rhs.capacity_]; capacity_ = rhs.capacity_; count_ = rhs.count_; memcpy (data_, rhs.data_, sizeof (T) * count_); return *this; } // end of operator= // destructor ~vector() { clear (); } // end of destructor T const & operator[] (unsigned int idx) const { return data_[idx]; } T& operator[] (unsigned int idx) { return data_[idx]; } void resize_to_fit() { resize(count_); } T& pop_back(){ return data_[--count_]; } void push_back(T obj) { if (capacity_ == count_) { resize(capacity_ + 1); } data_[count_++] = obj; } bool isempty() { return count_ == 0; } void clear() { delete [] data_; data_ = nullptr; count_ = capacity_ = 0; } T* data() { return data_; } int size() { return count_; } int capacity() { return capacity_; } private: void resize(int capacity) { if (data_ == nullptr) { data_ = new T[capacity]; count_ = 0; capacity_ = capacity; } else if (capacity > capacity_) { //else do nothing // allocate new memory T* data = new T[capacity]; // count can"t be higher than capacity count_ = min(count_, capacity); capacity_ = capacity; // copy data across to new pointer memcpy(data, data_, sizeof(T) * count_); // delete old pointer delete [] data_; // now remember the new pointer data_ = data; } } protected: int capacity_; int count_; T* data_; }; void print_vec(vector<int> v){ printf("%s\n", "Printing new vec!"); for( int i = 0; i < v.size(); i++){ printf("%i\n", v[i]); } } int main() { vector<int> i; i.push_back(10); i.push_back(2); print_vec(i); i.push_back(3); vector<int> j; // test assignment j = i; print_vec(j); print_vec(i); while(i.size () > 0){ print_vec(i); i.pop_back(); } }
Kommentarer
Svar
Det är något jag vill lägga till.
Andra har redan påpekat många fel i koden. Och jag kunde peka många fler, men det är inte poängen.
IMO, det största misstaget är att implementera en Vector-klass för Arduino!
Även om det fungerar är det bara en dålig idé. Tänk på att Arduino har mycket begränsat minne. Dynamisk minnesallokering i ett sådant begränsat utrymme låter bara som en dålig idé – du slösar bort minne, du fragmenterar högen och du får nästan ingenting, förutom viss kodningsbekvämlighet.
Jag har ännu inte sett ett fall där man verkligen behöver dynamisk minnestilldelning i ett Arduino-projekt. Och även om du gör det finns det många andra verktyg du kan använda (som stack-allokerade temporära buffertar, arenatilldelare, pooltilldelare och vad som helst). Men igen, förmodligen koden och minneskostnaden för dessa ”generiska” lösningar kommer att vara omöjliga på Arduino.
För det mesta är det bra att använda vektorer, kartor och vad du vill på plattformar som PC (jag jobbar som programmerare för PC-spel, så jag stöter ofta på situationer där tillbehör som dessa är stora ”nej-nej”, men för de flesta applikationer är det bra).
Men på en ”tät” plattform som Arduino, tror jag att du borde stanna ”nära metallen” och vara i total kontroll över vad som händer (minne och CPU-cykler klokt). Helst skulle du bara ha råd med ”luxuri es ”som dessa när du vet vad som händer” under ”och du vet att du kan komma undan med det. Vilket är sällan fallet på ”snäva” plattformar som Arduino.
Kommentarer
- Du har en poäng här. I synnerhet
push_back
-idén som stegvis tilldelar mer minne (vilket normalt är bra på en dator med 4 Gb RAM) kommer sannolikt att införa minnesfragmentering. Jag tror att även normala STL-implementeringar fördelar vektorminne (kapacitet i det här fallet) i satser (t.ex. 10 åt gången) för att minska problemet.
I din andra konstruktör börjar du med följande kod:
Detta är dödligt! Du har inte initierat
__data
, så det kan hålla något värde under solen. Och det finns inget sätt att det kan möjligen vara en giltig pekare till befintlig data – det är ett helt nytt oinitialiserat objekt. Att returnera denna sopor till högen ber helt enkelt om problem.Ta bort dessa rader – eller ännu bättre, använd en initialiseringslista som du gjorde med den första konstruktören:
Ett annat problem: I din
clear()
medlem som du skrev:Du borde aldrig, aldrig , aldrig kallar direkt till en destruktör som denna. Kompilatorn kan lägga alla möjliga andra koder i destruktorkoden, eftersom den ”vet” att den är den enda som någonsin kommer att kalla den. Till exempel trycker vissa kompilatorer på en flagga för att säga om de ska
delete
objektet från högen efter att ha gjort förstörelsen. Du har inte gjort det så ovan kan skada alla möjliga saker.Flytta istället koden som finns i förstöraren till
clear()
, och ring sedan helt enkeltclear()
från förstöraren.En annan, den här plockade upp av @Nick Gammon:
Detta testar om
__data
-medlemmen är falsk och omcapacity
ochsize
har definierats. Oroa dig inte: de två sistnämnda har varit … du saknade prefixet__
…Kommentarer
vector(T obj, int count = 1)
konstruktören och den kompilerades utan fel, så det var inte ’ t problemet i detta fall, men du har rätt att__data
inte initialiserades.should never have compiled
– det testade funktionerna snarare än att ringa dem.capacity
är en medlemsfunktion, inte en variabel. Jag tog upp det när jag försökte kompilera det underclang
.