I am working on implementing a vector class but cannot figure out how to write a function to copy one vector into another.
template <class T> class Vec {
public:
//TYPEDEFS
typedef T* iterator;
typedef const T* const_iterator;
typedef unsigned int size_type;
//CONSTRUCTOS, ASSIGNMENT OPERATOR, & DESTRUCTOR
Vec() {this->create(); }
Vec(size_type n, const T& t = T()) { this->create(n, t); }
Vec(const Vec& v) { copy(v); }
Vec& operator=(const Vec& v);
~Vec() { delete [] m_data; }
//MEMBER FUNCTIONS AND OTHER OPERATORS
T& operator[] (size_type i) { return m_data[i]; }
const T& operator[] (size_type i) const { return m_data[i]; }
void push_back (const T& t);
iterator erase(iterator p);
void resize(size_type n, const T& fill_in_value = T());
void clear() { delete [] m_data; create(); }
bool empty() const { return m_size == 0; }
size_type size() const { return m_size; }
//ITERATOR OPERATIONS
iterator begin() { return m_data; }
const_iterator begin() const { return m_data; }
iterator end() { return m_data + m_size; }
const_iterator end() const { return m_data + m_size; }
private:
//PRIVATE MEMBER FUNCTIONS
void create();
void create(size_type n, const T& val);
void copy(const Vec<T>& v);
//REPRESENTATION
T *m_data; //point to first location inthe allocated array
size_type m_size; //number of elements stored in the vector
size_type m_alloc; //number of array locations allocated, m_size <= m_alloc
};
//create an empty vector (null pointers everywhere)
template <class T> void Vec<T>::create() {
m_data = NULL;
m_size = m_alloc = 0; //no memory allocated yet
}
//create a vector with size n, each location having the given value
template <class T> void Vec<T>::create(size_type n, const T& val) {
m_data = new T[n];
m_size = m_alloc = n;
for (T* p = m_data; p != m_data + m_size; ++p)
*p = val;
}
//assign one vector to another, avoiding duplicate copying
template <class T> Vec<T>& Vec<T>::operator=(const Vec<T>& v) {
if (this != &v) {
delete [] m_data;
this -> copy(v);
}
return *this;
}
This is the first things I came up with:
template <class T> void Vec<T>::copy(const Vec<T>& v) {
m_size = m_alloc = v.size();
m_data = &v;
}
I got an error about incompatible types...OK, makes sense that they are incompatible. So I take out the 'const' and now it works.
template <class T> void Vec<T>::copy(Vec<T>& v) {
m_size = m_alloc = v.size();
m_data = &v[0];
}
I am guessing this isn't entirely correct or good form. I'm not sure. And now I get an error about the pointer being freed not having been allocated (but it compiles, runs, and copies the vector successfully at least now). So I would say I am not really understanding passing variables/arrays/vectors/things by reference, and also dynamic allocation of memory. My question is: how can I improve the copy function that I have written to either not compare two incompatible variables, or to successfully allocate the pointer dynamically to the new vector so that I don't get that malloc error?
You need to do deep-copy of the elements, not simply assign the pointer m_data:
// precondition: `m_data` is not allocated
template <class T> void Vec<T>::copy(const Vec<T>& v) {
m_data = new T[v.size()];
m_size = m_alloc = v.size();
for (size_t ii = 0; ii < m_size; ++ii)
m_data[ii] = v[ii];
}
For your copy-constructor to be safe it needs to fail when the copy can't be made.
Vec<T>::Vec(const Vec<T>& o):m_size(o.m_size), m_alloc(o.m_size), m_data(new T()){
std::copy( o.m_data, o.m_data+o.m_size, m_data);
}
The copy-constructor should be able to replace any Vec<T>::copy member.
The assignment is easily handled by introducing a swap function. This is exception safe.
void Vec<T>::swap(Vec<T>& rhs){
std::swap(m_data, rhs.m_data);
std::swap(m_size, rhs.m_size);
std::swap(m_capacity, rhs.m_capacity);
}
With the exception safe Copy & swap & idiom it becomes:
Vec<T>& Vec<T>::operator = (const Vec<T>& rhs){
Vec<T> cpy=rhs;
swap( this, cpy);
return *this;
}
Note that there is an issue with exception safety with the previous answer given. The simple fix is to allocate first.
// precondition: `m_data` is not allocated
template <class T> void Vec<T>::copy(const Vec<T>& v) {
m_data = new T[v.size()];
m_size = m_alloc = v.size();
for (size_t ii = 0; ii < m_size; ++ii)
m_data[ii] = v[ii];
}
The other issue with your code is operator=, which is not exception safe. You deleted m_data before allocating again with new[]. If new[] fails, you've corrupted your object.
Once you have the fix to copy as above, then operator = can be written in terms of the copy constructor:
template <class T> Vec<T>& Vec<T>::operator=(const Vec<T>& v)
{
// construct a temporary
Vec<T> temp = v;
// set the members
m_size = m_alloc = temp.size();
delete [] m_data;
m_data = temp.m_data;
// Now set temp.m_data to 0, so that destruction of temp doesn't delete
// our memory
temp.m_data = 0;
return *this;
}
Basically, we construct a temporary from v, delete the this->m_data and then assign the elements of temp to this. Then we remove the guts of temp by setting the temp.m_data data to NULL. This needs to be done so that when temp dies, we don't want to delete the data we assigned to this.
Note that if the first line Vec<T> temp = v; throws an exception, no harm to this is
done, thus exception safety is provided.
Here is the copy/swap idiom, as suggested by Captain Giraffe:
template <class T> class Vec {
//...
void swap(Vec<T>& left, Vec<T>& right);
//..
};
template <class T> void Vec<T>::swap(Vec<T>& left, Vec<T>& right)
{
std::swap(left.m_size, right.m_size);
std::swap(left.m_alloc, right.m_alloc);
std::swap(left.m_data, right.m_data);
}
template <class T> Vec<T>& Vec<T>::operator=(const Vec<T>& v)
{
// construct a temporary
Vec<T> temp = v;
swap(*this, temp);
return *this;
}
The difference here is that we're swapping the members of temp with this. Since temp will now contain the pointer that this used to have, when temp dies, it will be calling delete on this "old" data.
Related
I wrote my own Matrix class with such fields (you can't use STL containers in it)
template <typename T>
class Matrix
{
private:
T *data = nullptr;
size_t rows;
size_t cols;
I also made an iterator for this class:
public:
class Iterator
{
friend Matrix;
private:
T *curr;
public:
Iterator() : curr(nullptr) {}
Iterator(Matrix *matrix) : curr(matrix->data) {}
~Iterator() = default;
......
Iterator begin()
{
Iterator it(this);
std::cout << *it.curr << std::endl;
return it;
}
Iterator end()
{
Iterator it(this);
/*Iterator it(this->data + rows * cols);
if (it == nullptr)
{
return it;
}*/
return it;
}
How can I properly write an end() method that would return the next element after the last one? I understand that using address arithmetic you can get the latter, but how can I do it in my code?
I suggest that you let the iterator take a T* as argument. You can then supply data to the begin() iterator and data + rows * cols to the end() iterator.
Example:
template <class T>
class Matrix {
private:
T* data = nullptr;
size_t rows;
size_t cols;
public:
Matrix(size_t Rows, size_t Cols)
: data{new T[Rows * Cols]}, rows{Rows}, cols{Cols} {}
Matrix(const Matrix&) = delete;
Matrix(Matrix&&) = delete;
~Matrix() { delete[] data; }
class iterator {
private:
T* curr; // store a T*
public:
iterator(T* t) : curr{t} {} // take a T* and store it
iterator& operator++() { ++curr; return *this; }
bool operator==(const iterator& rhs) const { return curr == rhs.curr; }
bool operator!=(const iterator& rhs) const { return !(curr == rhs.curr); }
T& operator*() { return *curr; }
T* operator->() { return curr; }
};
iterator begin() { return {data}; } // begin() takes data
iterator end() { return {data + rows * cols}; } // end() takes data + rows *cols
};
Demo
However, you don't need to implement an iterator yourself for this. You can simply use pointers:
template <class T>
class Matrix {
private:
T* data = nullptr;
size_t rows;
size_t cols;
public:
Matrix(size_t Rows, size_t Cols)
: data{new T[Rows * Cols]}, rows{Rows}, cols{Cols} {}
Matrix(const Matrix&) = delete;
Matrix(Matrix&&) = delete;
~Matrix() { delete[] data; }
using iterator = T*; // iterator is now simply a T*
iterator begin() { return data; }
iterator end() { return data + rows * cols; }
};
I have to implement a vector class with operator[], rule of 5, push_back, and insert.
template<class T>
class MyVector
{
private:
int size_;
int capacity_;
T* data;
public:
typedef T* it;
it begin();
it end();
void push_back(const T& value);
I started to create constructor, copy constructor, move constructor, copy assignment operator, move assignment operator.
I implemented the begin() and end() like this:
template<class T>
typename MyVector<T>::it MyVector<T>::begin()
{
return data;
}
template<class T>
typename MyVector<T>::it MyVector<T>::end()
{
return data + size();
}
I don't know how to implement push_back() and insert().
For push_back(), I thought about something like this:
void push_back(const T & v)
{
if (size_ < capacity_)
data [size_++] = v;
}
But for insert(), I have no idea.
Could you help me please to implement insert()?
Your push_back() is not implemented correctly. It needs to grow the array when the size_ is equal to the capacity_, to make room for the new value being pushed.
Likewise, your insert() also needs to grow the array, if needed. It can then figure out the index where the new value should go, shift all of the elements after that index up one slot in the array, and then assign the new value to the element at that index.
Try something like this:
template<class T>
void MyVector<T>::grow()
{
static const int delta = ...; // <-- use whatever value makes sense for your use case...
int new_cap = capacity_ + delta;
T* new_data = new T[new_cap];
for(int i = 0; i < size_; ++i) {
new_data[i] = std::move(data[i]);
}
delete[] data;
data = new_data;
capacity_ = new_cap;
}
template<class T>
void MyVector<T>::push_back(const T & v)
{
if (size_ == capacity_)
grow();
data[size_] = v;
++size_;
}
template<class T>
void MyVector<T>::insert(typename MyVector<T>::it pos, const T & v)
{
int index = pos - data;
if (index < 0 || index > size_)
return; // or throw...
if (size_ == capacity_)
grow();
for(int i = size_ - 1; i >= index; --i)
data[i+1] = data[i];
data[index] = v;
++size_;
}
Live Demo
For insert you must.
make room for the extra data. (as you also need to do in push_back/emplace_back).
move the current data from the insertion point to the new offset.
copy the inserted data in.
From my previous question: How to implement erase on vector in c++, I have tried to implement erase via allocating new memory, and copying all elements except of that one erase to new array. From the answers, it is not a standard way of vector from STL, where the vector keeps old elements untouched, and only moves elements back after the one erased (this is part of the "invalidation" of previous iterators. So I have tried to use std::move():
#ifndef _vec_h
#define _vec_h
#include <stddef.h>
#include <memory>
#include <algorithm>
template<class T>
class Vec
{
public:
typedef T* iterator;
typedef T* const const_iterator;
typedef T value_type;
Vec() {
create();
}
explicit Vec(size_t n, const T& val = T()) {
create(n, val);
}
Vec(const Vec& v) {
create(v.begin(), v.end());
}
Vec& operator=(const Vec& v);
~Vec() {
uncreate();
}
T& operator[](size_t i) {
return data[i];
}
const T& operator[](size_t i) const {
return data[i];
}
void push_back(const T& val);
size_t size() const {
return avail - data;
}
iterator begin() {
return data;
}
const_iterator begin() const {
return data;
}
iterator end() {
return avail;
}
const_iterator end() const {
return avail;
}
void clear() {
if (data)
{
for (iterator i=avail; i!=data; i--)
i->~T();
}
avail=data;
}
std::allocator<T> alloc;
private:
iterator data;
iterator avail;
iterator limit;
void create();
void create(size_t n, const T& val);
void create(const_iterator b, const_iterator e);
void uncreate();
void grow();
void unchecked_append(const T& val);
};
template<class T> Vec<T>& Vec<T>::operator=(const Vec& rhs)
{
if (&rhs != this)
{
uncreate();
create(rhs.begin(), rhs.end());
}
return *this;
}
template<class T> void Vec<T>::push_back(const T& val)
{
if (avail == limit)
{
grow();
}
unchecked_append(val);
}
template<class T> void Vec<T>::create()
{
data = avail = limit = 0;
}
template<class T> void Vec<T>::create(size_t n, const T& val)
{
data = alloc.allocate(n);
limit = avail = data + n;
std::uninitialized_fill(data, limit, val);
}
template<class T> void Vec<T>::create(const_iterator i, const_iterator j)
{
data = alloc.allocate(j-i);
limit = avail = std::uninitialized_copy(i, j, data);
}
template<class T> void Vec<T>::uncreate()
{
if (data)
{
iterator i = avail;
while (i!=data)
{
alloc.destroy(--i);
}
alloc.deallocate(data, limit-data);
}
data=limit=avail=0;
}
template<class T> void Vec<T>::grow()
{
size_t new_size = std::max(2*(limit-data), ptrdiff_t(1));
iterator new_data = alloc.allocate(new_size);
iterator new_avail = std::uninitialized_copy(data, avail, new_data);
uncreate();
data = new_data;
avail = new_avail;
limit = data + new_size;
}
template<class T> void Vec<T>::unchecked_append(const T& val)
{
alloc.construct(avail++, val);
}
//---------------------------------------------------------
// here I am trying to implement the erase function with 3 pointers (data, avail, limit)
template<class T>
T* Vec<T>::erase(T *const i)
{
if(i==end())
{
return end();
}
else if(i >= begin() && i < end())
{
std::move(i+1, avail, i); //shift elements to the left
return i;
}
else
{
return 0;
}
}
#endif
Which still causes segfault. So is there any way, how to implement the erase?
To compile I needed to add this method declaration:
T* erase(T *const i);
I added some quick test code to recreate your segfault:
int main()
{
Vec<int> test;
test.push_back(1);
test.push_back(2);
test.push_back(3);
test.push_back(4);
test.erase(test.begin() + 2);
std::cout << test[2] << std::endl; // should output 4
}
However, the code outputed 4 as expected, and I did not get any segfault. I tried running in valgrind, and I didn't get any memory errors either. It appears to be working as you intended, but if you add some code to recreate the segfault, I will update the answer with a fix.
Im beginnger in C++ and try to creat container class which is similar to the Vector. this class should works like a Vector for all type of data and could be used in range-based for loop.
i wrote the hpp but my tutor says that there is a memory leak ,I think i deleted all the dynamic memory,where could be the problem?
#include "stdafx.h"
using namespace std;
template<class T>
class Customvector
{
public:
Customvector();
~Customvector();
int size();
int free_capacity();
void add(T& temp);
int& operator[](int index);
void grow();
class iterator {
public:
iterator(T* ptr) : ptr(ptr) {}
iterator operator++() { iterator i(ptr); ++ptr; return i; }
bool operator!=(const iterator & other) { return ptr != other.ptr; }
const T& operator*() const { return *ptr; }
private:
T* ptr;
};
iterator begin() const { return iterator(&_elements[0]); }
iterator end() const { return iterator(&_elements[0]+_size); }
private:
T* _elements;
int _size;
int _capacity;
int DEFAULT_CAPACITY;
};
template<class T>
Customvector<T>::Customvector()
{
DEFAULT_CAPACITY = 4;
_capacity = DEFAULT_CAPACITY;
_size = 0;
_elements = new T[_capacity];
}
template<class T>
Customvector<T>::~Customvector()
{
delete[] _elements;
}
template<class T>
void Customvector<T>::add(T& temp)
{
grow(); //check if the capacity is full, if so,increase capacity by DEFAULt_CAPACITY;
_elements[_size++]= temp;
}
template<class T>
int Customvector<T>::size()
{
return _size;
}
template<class T>
int Customvector<T>::free_capacity()
{
int free_c = _capacity - _size;
return free_c;
}
template<class T>
int& Customvector<T>::operator[](int index) {
if (index<0 || index>_capacity)
{
cout << "index beyond limit" << endl;
return _elements[0];
};
return _elements[index];
}
template<class T >
void Customvector<T>::grow()
{
if (_capacity == _size)
{
_capacity += DEFAULT_CAPACITY;
T* p = new T[_capacity];
std::copy(_elements, _elements + _size,p);
delete[] _elements;
_elements = p;
};
}
The only leaky case that I can find is in grow:
...
T* p = new T[_capacity];
std::copy(_elements, _elements + _size,p); // may throw an exception
delete[] _elements;
_elements = p;
...
If copying of a contained element throws, then _elements still points to the old array and the new array pointed by p leaks. You can resolve this with unique_ptr:
std::unique_ptr<T[]> p(new T[_capacity]);
std::copy(_elements, _elements + _size, p.get()); // it's OK if this throws, unique_ptr will take care of the memory
delete[] _elements;
_elements = p.release();
Using unique_ptr for _elements too would simplify some of your code and improve correctness.
I was making a copy function here, got a lot of good answers, now moving on to a resize function. The Vec class I'm working on looks like this:
template <class T> class Vec {
public:
//TYPEDEFS
typedef T* iterator;
typedef const T* const_iterator;
typedef unsigned int size_type;
//CONSTRUCTOS, ASSIGNMENT OPERATOR, & DESTRUCTOR
Vec() {this->create(); }
Vec(size_type n, const T& t = T()) { this->create(n, t); }
Vec(const Vec& v) { copy(v); }
Vec& operator=(const Vec& v);
~Vec() { delete [] m_data; }
//MEMBER FUNCTIONS AND OTHER OPERATORS
T& operator[] (size_type i) { return m_data[i]; }
const T& operator[] (size_type i) const { return m_data[i]; }
void push_back (const T& t);
void swap(Vec<T>& left, Vec<T>& right);
iterator erase(iterator p);
void resize(size_type n, const T& fill_in_value = T());
void clear() { delete [] m_data; create(); }
bool empty() const { return m_size == 0; }
size_type size() const { return m_size; }
//ITERATOR OPERATIONS
iterator begin() { return m_data; }
const_iterator begin() const { return m_data; }
iterator end() { return m_data + m_size; }
const_iterator end() const { return m_data + m_size; }
private:
//PRIVATE MEMBER FUNCTIONS
void create();
void create(size_type n, const T& val);
void copy(const Vec<T>& v);
//REPRESENTATION
T *m_data; //point to first location inthe allocated array
size_type m_size; //number of elements stored in the vector
size_type m_alloc; //number of array locations allocated, m_size <= m_alloc
};
//create an empty vector (null pointers everywhere)
template <class T> void Vec<T>::create() {
m_data = NULL;
m_size = m_alloc = 0; //no memory allocated yet
}
//create a vector with size n, each location having the given value
template <class T> void Vec<T>::create(size_type n, const T& val) {
m_data = new T[n];
m_size = m_alloc = n;
for (T* p = m_data; p != m_data + m_size; ++p)
*p = val;
}
//assign one vector to another, avoiding duplicate copying
template <class T> Vec<T>& Vec<T>::operator=(const Vec<T>& v) {
Vec<T> temp = v;
swap(*this, temp);
return *this;
}
//swap one vector with another
template <class T> void Vec<T>::swap(Vec<T>& left, Vec<T>& right) {
std::swap(left.m_size, right.m_size);
std::swap(left.m_alloc, right.m_alloc);
std::swap(left.m_data, right.m_data);
}
This is what the resize function looks like so far:
template <class T> void Vec<T>::resize(size_type n, const T& fill_in_value) {
if (n<=m_size) { //if resize is smaller than original size, just resize
m_size = n;
}
else { //if resize is bigger, assign more space at the end and fill with a value
size_type temp = m_size;
m_size = n;
//for (T* p = m_data; p != m_data + temp; ++p) {
// std::cout << p << std::endl;
//}
//std::cout << std::endl;
for (T* p = &m_data[0] + temp; p != m_data + m_size; ++p) {
// std::cout << p << std::endl;
*p = fill_in_value;
}
}
}
When I use this in a program, I get a segmentation fault. I'm guessing there is something wrong with:
*p = fill_in_value
So I print out the values of p and it seems to give the appropriate number of spaces for whatever I put in (8 bits for a double, etc), so from the beginning of the vector there are exactly the number of spaces to resize and they all have the right number of bits. What is it that I am not understanding here?
EDIT: If I change it so that:
m_size = m_alloc = n;
does that mean that I am allocating new memory for the rest of the data? Or should I create a temporary vector with the new size and replace the existing vector with that? I don't really understand how to correct the issue that I am not truly resizing, but just accessing out of bounds. The way I am understanding it so far is that I change the size, add stuff to the memory addresses at the end, but leave the pointer to the first value the same (the name of the vector). This is not working, so I'm wondering what my conceptual misunderstanding is.
EDIT2: I've made this correction to the code based on the answers here and it works:
template <class T> void Vec<T>::resize(size_type n, const T& fill_in_value) {
if (n<=m_size) {
m_size = m_alloc = n;
}
else {
T* new_data = new T[n];
for (size_type i = 0; i < m_size; i++) {
new_data[i] = m_data[i];
}
for (size_type i = m_size; i < n; i++) {
new_data[i] = fill_in_value;
}
delete m_data;
m_data = new_data;
m_size = m_alloc = n;
}
}
You haven't actually allocated space for new elements. When new size is bigger than current, you should allocate new storage and copy existing elements there.
T* new_data = new T[n];
for ( size_type i = 0; i < m_size; ++i )
{
new_data[i] = m_data[i];
}
for ( size_type i = m_size; i < n; ++i )
{
new_data[i] = fill_in_value;
}
delete m_data;
m_data = new_data;
m_size = n;