Реализация вектора в С++

Недавно я написал реализацию STL Vector в качестве упражнения по программированию. Программа компилируется, но я получаю странную ошибку:

terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc

Я никогда раньше не сталкивался с этой ошибкой и не уверен, что именно нужно изменить в моей реализации, чтобы она работала правильно.

Может ли кто-нибудь просмотреть мой код и посмотреть, не кажется ли им что-то неправильным в этом конкретном случае? Извините, я не могу быть более конкретным, я не знаю, где искать, заранее спасибо.

#include <iostream>
#include <string>
#include <cassert>
#include <algorithm>

using namespace std;

template <class T>
class Vector
{
public:

   typedef T * iterator;

   Vector();
   Vector(unsigned int size);
   Vector(unsigned int size, const T & initial);
   Vector(const Vector<T> & v);      
   ~Vector();

   unsigned int capacity() const;
   unsigned int size() const;
   bool empty() const;
   iterator begin();
   iterator end();
   T & front();
   T & back();
   void push_back(const T & value); 
   void pop_back();  

   void reserve(unsigned int capacity);   
   void resize(unsigned int size);   

   T & operator[](unsigned int index);  
   Vector<T> & operator=(const Vector<T> &);

private:
   unsigned int my_size;
   unsigned int my_capacity;
   T * buffer;
};

// Your code goes here ...
template<class T>
Vector<T>::Vector()
{
    my_capacity = 0;
    my_size = 0;
    buffer = 0;
}

template<class T>
Vector<T>::Vector(const Vector<T> & v)
{
    my_size = v.my_size;
    my_capacity = v.my_capacity;
    buffer = new T[my_size];  
    for (int i = 0; i < my_size; i++)
        buffer[i] = v.buffer[i];  
}

template<class T>
Vector<T>::Vector(unsigned int size)
{
    my_capacity = size;
    my_size = size;
    buffer = new T[size];
}

template<class T>
Vector<T>::Vector(unsigned int size, const T & initial)
{
    my_size-size;
    my_capacity = size;
    buffer = new T [size];
    for (int i = 0; i < size; i++)
        buffer[i] = initial;
        T();
}

template<class T>
Vector<T> & Vector<T>::operator = (const Vector<T> & v)
{
    delete[ ] buffer;
    my_size = v.my_size;
    my_capacity = v.my_capacity;
    buffer = new T [my_size];
    for (int i = 0; i < my_size; i++)
        buffer[i] = v.buffer[i];
    return *this;
}

template<class T>
typename Vector<T>::iterator Vector<T>::begin()
{
    return buffer;
}

template<class T>
typename Vector<T>::iterator Vector<T>::end()
{
    return buffer + size();
}

template<class T>
T& Vector<T>::Vector<T>::front()
{
    return buffer[0];
}

template<class T>
T& Vector<T>::Vector<T>::back()
{
    return buffer[size - 1];
}

template<class T>
void Vector<T>::push_back(const T & v)
{
    if (my_size >= my_capacity)
    reserve(my_capacity +5);
    buffer [my_size++] = v;
}

template<class T>
void Vector<T>::pop_back()
{
    my_size--;
}

template<class T>
void Vector<T>::reserve(unsigned int capacity)
{
    if(buffer == 0)
    {
        my_size = 0;
        my_capacity = 0;
    }    
    T * buffer = new T [capacity];
    assert(buffer);
    copy (buffer, buffer + my_size, buffer);
    my_capacity = capacity;
    delete[] buffer;
    buffer = buffer;

}

template<class T>
unsigned int Vector<T>::size()const//
{
    return my_size;
}

template<class T>
void Vector<T>::resize(unsigned int size)
{
    reserve(size);
    size = size;
}

template<class T>
T& Vector<T>::operator[](unsigned int index)
{
    return buffer[index];
}  

template<class T>
unsigned int Vector<T>::capacity()const
{
    return my_capacity;
}

template<class T>
Vector<T>::~Vector()
{
    delete[]buffer;
}


int main()
{  

   Vector<int> v;

   v.reserve(2);
   assert(v.capacity() == 2);

   Vector<string> v1(2);
   assert(v1.capacity() == 2);
   assert(v1.size() == 2);
   assert(v1[0] == "");
   assert(v1[1] == "");

   v1[0] = "hi";
   assert(v1[0] == "hi");

   Vector<int> v2(2, 7);
   assert(v2[1] == 7);

   Vector<int> v10(v2);
   assert(v10[1] == 7);

   Vector<string> v3(2, "hello");
   assert(v3.size() == 2);
   assert(v3.capacity() == 2);
   assert(v3[0] == "hello");
   assert(v3[1] == "hello");

   v3.resize(1);
   assert(v3.size() == 1);
   assert(v3[0] == "hello");

   Vector<string> v4 = v3;
   assert(v4.size() == 1);
   assert(v4[0] == v3[0]);
   v3[0] = "test";
   assert(v4[0] != v3[0]);  
   assert(v4[0] == "hello");

   v3.pop_back();
   assert(v3.size() == 0);

   Vector<int> v5(7, 9);
   Vector<int>::iterator it = v5.begin();
   while (it != v5.end())
   {
      assert(*it == 9);
      ++it;
   }

   Vector<int> v6;
   v6.push_back(100);
   assert(v6.size() == 1);
   assert(v6[0] == 100);
   v6.push_back(101);
   assert(v6.size() == 2);
   assert(v6[0] == 100);
   v6.push_back(101);

   cout << "SUCCESS\n";
}

person UndefinedReference    schedule 01.03.2011    source источник
comment
Вы должны запустить свою программу с помощью отладчика, чтобы вы могли видеть, где возникает это исключение.   -  person tibur    schedule 01.03.2011
comment
cplusplus.com/reference/std/new/bad_alloc   -  person    schedule 01.03.2011
comment
можете ли вы сузить строку в main(), после которой вы получаете сообщение об ошибке?   -  person Dan    schedule 01.03.2011
comment
Я бы посоветовал вам создать модульный тест по крайней мере для каждого метода.   -  person ronag    schedule 01.03.2011
comment
on Vector<T>::Vector(unsigned int size, const T & initial) Что это T() у тебя там?   -  person    schedule 01.03.2011


Ответы (5)


Вот полный исходный код, обновленный из вашего источника:

    #pragma once


//using namespace std;

template <class T>
class  Vector
{
public:

    typedef T * iterator;

    Vector();
    Vector(unsigned int size);
    Vector(unsigned int size, const T & initial);
    Vector(const Vector<T> & v);      
    ~Vector();

    unsigned int capacity() const;
    unsigned int size() const;
    bool empty() const;
    iterator begin();
    iterator end();
    T & front();
    T & back();
    void push_back(const T & value); 
    void pop_back();  

    void reserve(unsigned int capacity);   
    void resize(unsigned int size);   

    T & operator[](unsigned int index);  
    Vector<T> & operator=(const Vector<T> &);
    void clear();
private:
    unsigned int my_size;
    unsigned int my_capacity;
    T * buffer;
};

// Your code goes here ...
template<class T>
Vector<T>::Vector()
{
    my_capacity = 0;
    my_size = 0;
    buffer = 0;
}

template<class T>
Vector<T>::Vector(const Vector<T> & v)
{
    my_size = v.my_size;
    my_capacity = v.my_capacity;
    buffer = new T[my_size];  
    for (unsigned int i = 0; i < my_size; i++)
        buffer[i] = v.buffer[i];  
}

template<class T>
Vector<T>::Vector(unsigned int size)
{
    my_capacity = size;
    my_size = size;
    buffer = new T[size];
}

template<class T>
Vector<T>::Vector(unsigned int size, const T & initial)
{
    my_size = size;
    my_capacity = size;
    buffer = new T [size];
    for (unsigned int i = 0; i < size; i++)
        buffer[i] = initial;
    //T();
}

template<class T>
Vector<T> & Vector<T>::operator = (const Vector<T> & v)
{
    delete[ ] buffer;
    my_size = v.my_size;
    my_capacity = v.my_capacity;
    buffer = new T [my_size];
    for (unsigned int i = 0; i < my_size; i++)
        buffer[i] = v.buffer[i];
    return *this;
}

template<class T>
typename Vector<T>::iterator Vector<T>::begin()
{
    return buffer;
}

template<class T>
typename Vector<T>::iterator Vector<T>::end()
{
    return buffer + size();
}

template<class T>
T& Vector<T>::front()
{
    return buffer[0];
}

template<class T>
T& Vector<T>::back()
{
    return buffer[my_size - 1];
}

template<class T>
void Vector<T>::push_back(const T & v)
{
    if (my_size >= my_capacity)
        reserve(my_capacity +5);
    buffer [my_size++] = v;
}

template<class T>
void Vector<T>::pop_back()
{
    my_size--;
}

template<class T>
void Vector<T>::reserve(unsigned int capacity)
{
    if(buffer == 0)
    {
        my_size = 0;
        my_capacity = 0;
    }    
    T * Newbuffer = new T [capacity];
    //assert(Newbuffer);
    unsigned int l_Size = capacity < my_size ? capacity : my_size;
    //copy (buffer, buffer + l_Size, Newbuffer);

    for (unsigned int i = 0; i < l_Size; i++)
        Newbuffer[i] = buffer[i];

    my_capacity = capacity;
    delete[] buffer;
    buffer = Newbuffer;
}

template<class T>
unsigned int Vector<T>::size()const//
{
    return my_size;
}

template<class T>
void Vector<T>::resize(unsigned int size)
{
    reserve(size);
    my_size = size;
}

template<class T>
T& Vector<T>::operator[](unsigned int index)
{
    return buffer[index];
}  

template<class T>
unsigned int Vector<T>::capacity()const
{
    return my_capacity;
}

template<class T>
Vector<T>::~Vector()
{
    delete[ ] buffer;
}
template <class T>
void Vector<T>::clear()
{
    my_capacity = 0;
    my_size = 0;
    buffer = 0;
}
person Phu Ta    schedule 24.10.2012
comment
return buffer[size-1]; должно быть return buffer[my_size-1]; в методе T& Vector<T>::back(). - person Fredrick Gauss; 10.03.2013
comment
pop_back должен освободить все ресурсы, которые были у последнего элемента, иначе вы обречены на утечку памяти. Сделай это buffer[my_size]->~T(); --my_size; - person Nelson Vides; 05.12.2018

Может это опечатка?

Vector<T>::Vector(unsigned int size, const T & initial)
{
    my_size-size; 
person Mark Ransom    schedule 01.03.2011
comment
@ Марк Рэнсом, нет, это все еще оставляет меня с той же ошибкой. Спасибо, но это было неправильно. - person UndefinedReference; 01.03.2011
comment
@Meursault: есть также size = size; в resize, который, вероятно, должен быть my_size = size;. И buffer = buffer; в reserve должно быть this->buffer = buffer;. - person casablanca; 01.03.2011

Ваш "запас" нарушен. Используйте другое имя переменной для локального буфера.

person Erik    schedule 01.03.2011
comment
чтобы быть более ясным, вы никогда не устанавливаете this.buffer, потому что ваша локальная переменная называется буфером. Таким образом, вы никогда не резервируете место, но продолжаете утекать память при каждом вызове резервирования. - person Kevin; 01.03.2011

В дополнение к необходимости исправить вашу функцию reserve, у вашего конструктора копирования и оператора присваивания копирования есть интересная проблема:

Vector<T> t1 = t2;

Это установит мощность t1 равной мощности (переменной) t2, но фактическая мощность t1 будет равна размеру t2; Таким образом, когда вы начинаете помещать элементы в вектор после конструктора копирования/оператора присваивания, у вас возникает проблема переполнения буфера.

Вам нужно изменить его на

template<class T>
Vector<T>::Vector(const Vector<T> & v)
{
    my_size = v.my_size;
    my_capacity = v.my_capacity;
    buffer = new T[my_capacity];
    memcpy(buffer, v.buffer, my_size * sizeof(T));  
}

ИЛИ (если вы хотите разрешить изменение размера до меньшего массива)

template<class T>
Vector<T>::Vector(const Vector<T> & v)
{
    my_size = v.my_size;
    my_capacity = v.my_size;
    buffer = new T[my_size];
    memcpy(buffer, v.buffer, my_size * sizeof(T));  
}
person Zac Howland    schedule 01.03.2011

Этот код не скомпилировался для меня. Clang пожаловался, что в строке 114 (реализация back() ) ожидается вызов «size».

Я думаю, что строка должна была быть «return buffer[size() -1];»

Он также выдает предупреждения о реализации этого конструктора: template Vector::Vector(unsigned int size, const T & initial)

Первая строка, вероятно, должна была быть "my_size = size;" Последняя строка (этого конструктора), вероятно, должна быть удалена.

Затем он не выполняет утверждение в строке 209: assert(v3.size() == 1);

Это открывает целую банку червей, но очевидная проблема заключается в resize() в строке: "size = size;" что, вероятно, означает «my_size = size;»

С этим изменением теперь происходит сбой в строке 121, которая находится в функции push_back(), вызываемой из строки 231 "v6.push_back(100);"

Это не удается из-за проблем в резерве(). Мы создаем локальную переменную «буфер» с тем же именем, что и у переменной-члена. Давайте изменим имя на temp_buffer. Примечание. Не используйте assert() во время выполнения. assert() предназначен для логических ошибок. Этот assert() не может потерпеть неудачу. new никогда не вернет 0. Вместо этого он выдаст .

После внесения очевидных исправлений в резерв() (есть и другие проблемы), теперь у нас происходит сбой в функции копирования() в резерве() при вызове из resize(), вызванном из lin3 208 в main(), "v3.resize( 1);».

Мы видим, что резерв фактически выделяет новый буфер, когда мы уменьшаем емкость. Это и потеря производительности, и потеря надежности (может произойти сбой выделения памяти). Но мы все равно не должны падать, поэтому мы постараемся предотвратить крах, не исправляя очевидный недостаток дизайна.

Сбой происходит из-за того, что мы копируем все элементы, которые существуют в контейнере, во вновь выделенный массив. Это было бы правильно, если бы мы делали это только тогда, когда нам нужно увеличить нашу емкость, но в этом случае у нас больше предметов, чем может вместить наша новая емкость. Код должен установить для my_size новую емкость, если она больше этого значения.

Теперь тестовый код сообщает «УСПЕХ».

Однако с этим кодом по-прежнему много проблем.

Одна из самых больших проблем заключается в том, что мы не используем неинициализированную память в выделенном массиве. Выполнение этого требуется стандартом для std::vector и имеет преимущества как в производительности, так и в надежности. Но это также усложняет код, и поэтому это может быть сокращением, с которым мы можем жить для того, что, очевидно, является интеллектуальным упражнением.

Конструкторы: используйте синтаксис инициализатора для инициализации элементов данных.

С вашим конструктором копирования и вашим конструктором из начального значения вы потеряете выделенный массив, если какое-либо из ваших зацикленных назначений вызовет исключение.

Оператор присваивания должен выделять новый буфер размером «my_capacity», а не «my_size», хотя существует очевидная оптимизация, заключающаяся в том, что если размер правого объекта не больше, чем «этот» объект, мы не должны вообще выделять.

Если выделение нового массива не удается в операторе присваивания, мы уже удалили буфер, поэтому в конечном итоге (когда наш объект Vector будет уничтожен) мы получим двойное удаление буфера, и до этого у нас может быть весь ад.

В push_back(), чтобы поддерживать гарантии производительности стандарта, нам нужно увеличить емкость на некоторую постоянную долю размера существующей емкости. Например, что-то вроде: «резерв (my_capacity * 1,5);»

person Jon Kalb    schedule 26.04.2012