2016-06-03 15 views
1

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

В функции void Stack :: push (const T & item), если я раскомментирую данные удаления []; строка, мой код работает хорошо, когда аргументом шаблона является, например, int или char. Но с std :: string я получаю странные ошибки памяти.

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

Теперь, когда я комментирую строку удаления, код работает хорошо даже с std :: string, но я не вижу, почему я не могу безопасно выполнять операции со всеми типами.

Любые идеи будут оценены.

#include <iostream> 
#include <stdio.h> 
#include <memory.h> 

template<class T> 
class Stack 
{ 
    T* data; 
    int sz; 

public: 
    //Stack(){sz=0;} 
    Stack(const std::initializer_list<T>&); 
    ~Stack(); 

    void push(const T&); 
    T& pop(); 

    void show() const; 
}; 

template<class T> 
Stack<T>::Stack(const std::initializer_list<T> &list) 
{ 
    sz=0; 
    data = new T[list.size()]; 

    for (auto i : list) { 
     data[sz] = i; 
     ++sz; 
    } 
    std::cout<< "Created with sz: "<< sz<<std::endl; 
} 

template<class T> 
Stack<T>::~Stack() 
{ 
    delete [] data; 
} 

template<class T> 
void Stack<T>::push(const T& item) { 
    std::cout<<"push "<<item<<std::endl; 
    T* arr = new T[sz]; 
    memcpy(arr, data, sz*sizeof(T)); 
    //delete [] data; 
    data = new T[sz + 1]; 
    memcpy(data, arr, sz*sizeof(T)); 
    ++sz; 
    data[sz - 1] = item; 
    std::cout<<"new size: "<<sz<<", bytes: "<<sz*sizeof(T)<<std::endl; 
} 

template<class T> 
T& Stack<T>::pop() 
{ 
    if(sz > 0) { 
     std::cout<<"pop "<<data[sz-1]<<std::endl; 
     std::cout<<"new size: "<<sz-1<<std::endl; 
     return data[--sz]; 
    } 
    else 
     return data[0]; 
} 

template<class T> 
void Stack<T>::show() const 
{ 
    for (int i=0; i<sz; i++) { 
     std::cout<<data[i]<<" "; 
    } 
    std::cout<<std::endl; 
} 

int main(){ 
    Stack<int> s = {1,2,3,4,5,6,7,8,9,10,11}; 
    s.show(); 
    s.push(12); 
    s.push(13); 
    s.push(14); 
    s.pop(); 
    s.pop(); 
    s.push(15); 
    s.push(16); 
    s.show(); 
    Stack<std::string> d = {"one","two","three"}; 
    d.show(); 
    d.pop(); 
    d.push("four"); 
    d.show(); 
    return 0; 
} 
+3

Calling 'delete []' вызывает деструктор объектов в массиве, но вы не хотите, чтобы их уничтожали, поскольку вы просто перемещаете их. В конце концов деструктор вызывается дважды, что приводит к ошибкам. –

+0

Я был под ложным впечатлением от того, что на самом деле делает delete []. Спасибо за объяснение. –

ответ

2

Не используйте memcpy копировать объекты, которые будут копировать биты в порядке, но для какого-либо объекта побитового копия не является правильным, как конструктор копирования (или копирующего оператора присваивания) не будет использоваться.

Хорошим и простым примером является то, что у вас есть стек из std::string объектов. Когда вы делаете побитую копию (с memcpy), содержимое std::stringобъектов копируется, но это в основном просто указатель и размер. Когда вы сделаете побитую копию, у вас будет два объекта std::string, ссылающихся на одну и ту же память. Уничтожение одного из этих объектов приведет к тому, что другой будет иметь блуждающий указатель на некоторую память (которая использовалась, чтобы содержать строку), которая больше не принадлежит вашей программе.

Чтобы решить эту проблему, используйте std::copy, вместо этого скопируйте объекты, они будут делать правильные вещи.


Unrelated к вашей проблеме, но ваша push функция делает копию, что она не нуждается:

T* arr = new T[sz]; 
memcpy(arr, data, sz*sizeof(T)); 

Это просто не нужно, вместо того, чтобы сделать что-то вроде

T* oldData = data; 
data = new T[sz + 1]; 

// Copy from old array to new 
std::copy(oldData, oldData + sz, data); 

delete[] oldData; 
+0

Спасибо за ответ и пример, это очень помогло. –

+1

Для разъяснения для будущих читателей была небольшая ошибка при использовании std :: copy выше. Правильный формат - std :: copy (oldData, oldData + sz, data) ;, поскольку параметры - это итераторы от начала источника, конца источника, начала назначения. С этим исправлением использование std :: copy решило мою проблему. –

+0

@DonBaker Правильно, спасибо, что заметили. :) –