Implementacja kontenera std::vector.

0

Próbuję napisać własną klasę przypominającą w działaniu standardowy wektor STL. Mam już szkielet całej klasy, ale gdy dodaję do wektora obiekt typu std::string, program wiesza się tuż po zakończeniu wykonywania metody void List::add(const T &).
Debugging oczywiście nic nie daje, ponieważ crash następuje tak jakby w procesie powrotu do poprzednio wykonywanego kodu.

Kontener:

template <class T>
class List {
private:
    int length;
    int capacity;
    int log;
    T *buffer;

    void reserve(int capacity) {
        T newBuffer[capacity];
        for(int i=0; i<length; i++) {
            newBuffer[i] = buffer[i];
        }
        delete[] buffer;
        buffer = newBuffer;
        this->capacity = capacity;
    }
public:
    List() {
        clear();
    }

    T &operator[](int index) {
        return buffer[index];
    }

    T *begin() { return buffer; }
    T *end() { return buffer + length; }
    T &front() { return buffer[0]; }
    T &back() { return buffer[length - 1]; }
    int size() {
        return length;
    }
    bool empty() {
        return length == 0;
    }
    void clear() {
        length = capacity = log = 0;
        buffer = nullptr;
    }
    int find(T element) {
        for(int i=0; i<length; i++) {
            if(buffer[i] == element)return i;
        }
        return -1;
    }
    void add(const T &element) {
        if(length == capacity)reserve(0b1 << log++);
        buffer[length++] = element;
        printf("SECOND\n");
    }
    T &get(int index) {
        return buffer[index];
    }

    ~List() {
        delete[] buffer;
    }
};

I kod testujący:

List<std::string> list;
printf("FIRST\n");
list.add(std::string("rgergcgreg fadfg\nsadfg"));
printf("THIRD\n");
//list.add(std::string("ailkrgksdferv fg "));
//list.add(std::string("asdgfcfg sadf xc a"));
printf("%i\n", list.size());
for(std::string str : list) {
    printf("%s\n", str.c_str());
}
1
    void reserve(int capacity) {
        T newBuffer[capacity];
        for(int i=0; i<length; i++) {
            newBuffer[i] = buffer[i];
        }
        delete[] buffer;
        buffer = newBuffer;
        this->capacity = capacity;
    }

Masz VLA (nie C++), a więc tablicę na stosie. Przypisujesz ją do wskaźnika buffer, po czym kończysz czas życia tej tablicy. Potem to już czyste UB, szczególnie jak na takim wskaźniku wywołujesz delete[].

Na przyszłość polecam użycie address sanitizera, świetne narzędzie do takich przypadków.

0

Dzięki! Ale ze mnie debil xD T *newBuffer = new T[capacity];

1
void clear() {
    length = capacity = log = 0;
    buffer = nullptr;
}

To będzie powodować wyciek pamięci. Przypisanie `nullptr` nie załatwia sprawy, pamięć musisz zwolnić.

PS. zainteresuj się *placement new*.
0

Kilka komentarzy.

  • Tworzysz tablicę (T *newBuffer = new T[capacity];) a sądząc po nazwie reserved i podobieństwa to std::vector tak na prawdę chcesz zaalokować pamięć i nic więcej. Teraz będziesz niepotrzebnie inicjował wszystkie obiekty w tablicy.
  • Zamiast pętli for do kopiowania użyj std::copy
  • Jeśli podczas kopiowania T::operator=() rzuci wyjątek to masz wyciek pamięci
  • Implementacja metody find - już coś takiego powstało: std::find
  • Pomyśl czy nie przeciążyć metody add i wykorzystać dobrodziejstwo r-values
  • Część metod może być const
  • Możesz się pozbyć zmiennej log i wykorzystać capacity * 2

1 użytkowników online, w tym zalogowanych: 0, gości: 1