im trying to create a custom vector class in c++. As im quite new to the whole c++ world im a bit confused. Valgrind is telling me that im doing a jump on an uninitialised value. However I don't know why that appears.
My personal answer would be alright i dont initialize any values in the normal
vector(unsigned startCap = 1, unsigned increment = 0):
width(increment),
cap(startCap),
field(new T[startCap]){};
However im completely clueless as this happens during the use of my operator= where i compare *this with my input.
Maybe someone could be so clever and tell me what i should do here.
template<class T>
class vector{
public:
vector(unsigned startCap = 1, unsigned increment = 0):
width(increment),
cap(startCap),
field(new T[startCap]){};
~vector(){
delete[] field;
};
vector(const vector& x):width(0),cap(1),field(new T[1]){
copyHelper(x);
};
vector(const T& x):width(0),cap(1),field(new T[1]){
push_back(x);
}
void copyHelper(const vector &v) {
for(unsigned i = 0; i < next; ++i) {
setField(v,i);
}
}
void push_back(T x){
if(next >= cap)
resize();
getField()[next++]=x;
}
T& operator[](unsigned x){
return getField()[x];
};
const T& operator[](unsigned x) const {
if(x < size()) {
return getField()[x];
}
}
bool operator==(const vector<T> v) const{
if((getCap() == v.getCap()) && (getNext() == v.getNext()) && (getWidth() == v.getWidth())){
for(unsigned i = 0; i<v.getCap();++i){
if(getField()[i] != v.getField()[i]){
return false;
}
}
return true;
}else{
return false;
}
}
vector& operator=(const vector& x){
if(*this == x)
return *this;
setCap(x.getCap());
setNext(x.getNext());
copyHelper(x);
return *this;
};
T* getField() const{
return field;
}
void setField(const vector<T>& x,unsigned i){
field[i] = x.getField()[i];
}
void setField(const T* x){
field = x;
}
...
private:
void resize(){
const unsigned newCap = width == 0 ? cap * 2 : cap + width;
T* newField = new T[newCap];
for(unsigned i = 0; i<cap;++i){
newField[i] = field[i];
}
delete[] field;
cap = newCap;
field = newField;
}
const unsigned width;
unsigned cap;
unsigned next = 0;
T* field = nullptr;
};
There are a lot of problems with this code.
Most notably, your operator== has some logic errors in it. But more importantly, if(*this == x) in operator= should be if(this == &x) instead.
Also, your copy constructor is implemented wrong. It allocates room for only 1 element, but then attempts to copy as many elements are in the source vector.
And, your const operator[] is missing a return or throw if the input x is out of bounds. It should not be performing anyvbounds checking at all, like your non-const operator[].
And, setField() looks like a memory leak waiting to happen.
And, we can't even see the rest of your implementation, so there is no way to see if there are other problems.
Related
So I'm trying to write my own array template and everything works until i try to create a const object of my class template. in main.cpp I create the object with the copy contructor and I change it which I would expect to not work but it works. Help would be appreciated :D
main.cpp
# include "Array.hpp"
int main( void ) {
Array<int> l = 1;
l.setValue(5, 0);
const Array<int> abc(l);
std::cout << abc[0] << std::endl;
abc[0] = 3;
std::cout << abc[0] << std::endl;
return (0);
}
Array.tpp
#ifndef ARRAY_TPP
# define ARRAY_TPP
# include "Array.hpp"
template<class T>
class Array {
private:
int size_;
T *array_;
public:
Array() : size_(0), array_(new T[size_]) {};
Array(int n) : size_(n), array_(new T[size_]) {};
Array(Array const& src) : size_(src.size()), array_(new T[src.size()]) {
for (int i = 0; i < src.size(); ++i) {
array_[i] = src[i];
}
};
Array& operator=(Array const& copy) {
size_ = copy.size();
delete[] array_;
array_ = new T[size_];
for (int i = 0; i < size_; i++)
array_[i] = copy[i];
return (*this);
}
T& operator[](int n) const {
if (n < 0 || n >= size_)
throw std::out_of_range("out of range");
return (array_[n]);
}
int size(void) const { return (size_); };
void setValue(T value, int n) {
if (n < 0 || n >= size_)
throw std::out_of_range("out of range");
array_[n] = value;
}
~Array() { delete[] array_; };
};
#endif
The issue is this:
T& operator[](int n) const {
if (n < 0 || n >= size_)
throw std::out_of_range("out of range");
return (array_[n]);
}
Because this is declared to be a const method, it can be called on a const Array. Though, it returns a non-const reference to the element. Because Array stores the elements via a T *, only that pointer is const in a const Array while modifiying the elements via that pointer is "fine".
You need two overloads of operator[]:
T& operator[](int n);
const T& operator[](int n) const;
I am trying to make a sorting function for a ring buffer class object, but it gives me the below error and I couldn't remove it:
template placeholder type 'ring' must be followed by a simple declarator-id
argument list for class template "ring" is missing
'arr' was not declared in this scope
(arr) was declared in the parameter of the function but yet it does not recognize it
hopefully someone can explain what is wrong with the sorting function, here is below my code:
#include<iostream>
#include<initializer_list>
template<typename T>
void swap(T& x, T& y) {
T temp;
temp = x;
x = y;
y = temp;
}
template<typename T>
class ring {
public:
class iterator;
public:
ring(std::initializer_list<T>elements) {
sze = elements.size();
value = new T[sze];
for (auto& i : elements) {
add(i);
}
}
T& get(int x) { return value[x]; }
std::size_t size() { return sze; }
void add(T v) {
value[index++] = v;
if (index == sze)
index = 0;
}
iterator begin() { return iterator(0, *this); }
iterator end() { return iterator(sze, *this); }
friend void BubbleSort(ring& arr, std::size_t n);
~ring() { delete[] value; }
private:
int index = 0;
std::size_t sze;
T* value;
};
template<typename T>
class ring<T>::iterator {
public:
iterator(int x, ring& y) :index(x), obj(y) {}
iterator& operator++() { index++; return *this; }
iterator& operator++(int x) { index++; return *this; }
bool operator!=(const iterator& other) { return index != other.index; }
T& operator*() { return obj.get(index); }
private:
int index;
ring& obj;
};
template<typename T>
void BubbleSort(ring& arr, std::size_t n) { // here is the function of the sorting.
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr.value[j] > arr.value[j + 1]) {
swap(arr.value[j], arr.value[j + 1]);
}
}
}
}
Your friendly compiler showed you already the problem and in which line it happened.
And it gave you 3 error messages telling you exactly what the problem is.
See my compiler output
Your class ring needs a template parameter. So same as in line 49 of the picture.
The below will compile.
#include<iostream>
#include<initializer_list>
template<typename T>
void swap(T& x, T& y) {
T temp;
temp = x;
x = y;
y = temp;
}
template<typename T>
class ring {
public:
class iterator;
public:
ring(std::initializer_list<T>elements) {
sze = elements.size();
value = new T[sze];
for (auto& i : elements) {
add(i);
}
}
T& get(int x) { return value[x]; }
std::size_t size() { return sze; }
void add(T v) {
value[index++] = v;
if (index == sze)
index = 0;
}
iterator begin() { return iterator(0, *this); }
iterator end() { return iterator(sze, *this); }
friend void BubbleSort(ring& arr, std::size_t n);
~ring() { delete[] value; }
private:
int index = 0;
std::size_t sze;
T* value;
};
template<typename T>
class ring<T>::iterator {
public:
iterator(int x, ring& y) :index(x), obj(y) {}
iterator& operator++() { index++; return *this; }
iterator& operator++(int x) { index++; return *this; }
bool operator!=(const iterator& other) { return index != other.index; }
T& operator*() { return obj.get(index); }
private:
int index;
ring& obj;
};
template<typename T>
void BubbleSort(ring<T>& arr, std::size_t n) { // here is the function of the sorting.
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr.value[j] > arr.value[j + 1]) {
swap(arr.value[j], arr.value[j + 1]);
}
}
}
}
I did not check the functionality. Sorting a ringbuffer sounds somehow strange . . .
Error C2676 binary '[': 'collection::item' does not define this operator or a conversion to a type acceptable to the predefined operator
I read few posts and Microsoft VS site, but cannot understand how to fix the problem
template<typename T1, typename T2>
class collection {
private:
class item {
public:
T1 item; T2 key;
};
unsigned int top;
item array = new item[top];
public:
collection& operator[](unsigned int i) {
return array[i];
}
collection(int top) {
this->top = top;
}
void coutarr() {
for (int i = 0; array[i] != 0; i++) {
cout << array[i].item << endl;
}
}
void extendarray() {
item x = new item[top*2];
for (int i = 0; i < top; i++) {
x[i] = array[i];
}
delete []array;
swap(array, x);
top = top*2;
}
void addvar(int i, T1 item, T2 key) {
array[i].item = item; array[i].key = key; //Here 2 Errors
}
};
If someone can explain what shall I do, I'd be very grateful. Thanks.
Error C2676 binary '[': 'collection::item' does not define this operator or a conversion to a type acceptable to the predefined operator
this is because
item array = new item[top];
must be
item * array = new item[top];
but you also have to move the initialization in the constructor when top has a value
Out of that is was better that the constructor collection(int top) get an unsigned value being collection(unsigned top) or better collection(size_t top)
Copy contructor, operator= ... are missing while an attribute is a pointer
What about to check the validity of i in collection& operator[](unsigned int i) and void addvar(int i, T1 item, T2 key) to produce an exception when it is invalid ? addvar is also a strange name because it is a set, a add means the size is increased. You cannot access to an element on a const instance, add const collection& operator[](unsigned int i) const
In void extendarray() to double the size will not do a lot if the initial size is 0 ;-)
Just a guess: top is unititialized. That makes array an array with zero length at best (which is really bad) and invokes undefined behavior at worst.
It seems you mean at least something like the following
template<typename T1, typename T2>
class collection {
private:
class item {
public:
T1 item; T2 key;
};
unsigned int top;
item *array;
^^^^^^^^^^^^^
public:
auto & operator[](unsigned int i) {
^^^^
return array[i];
}
collection(unsigned int top) : top( top ), array( new item[top]{} )
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
{
}
~collection() { delete []array; }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
collection( const collection & ) = delete;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
collection & operator =( const collection & ) = delete;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
void coutarr() {
for (int i = 0; array[i] != 0; i++) {
cout << array[i].item << endl;
}
}
void extendarray() {
item x = new item[top*2];
for (int i = 0; i < top; i++) {
x[i] = array[i];
}
delete []array;
swap(array, x);
top = top*2;
}
void addvar(int i, const T1 &item, const T2 &key) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
array[i].item = item; array[i].key = key; //Here 2 Errors
}
};
Of course you can futher develop the code.
Having an issue when trying to pass a const reference value within a range based for loop to a
function that takes a const reference value. Look at commments within the range based for loop to see what I'm talking about. It doesn't seem to be an issue with my implementation of 'unordered_vector' which is what I thought at first since it prints correctly outside the function. Why does this fail? It works if I pass by value, so I'm a bit confused here. Looks like it is referencing the reference which doesn't seem to be correct behaviour.
void println(const vec3& p)
{
std::cout << '<' << p.x << ',' << p.y << ',' << p.z << '>' << std::endl;
}
void printlnvalue(vec3 p)
{
std::cout << '<' << p.x << ',' << p.y << ',' << p.z << '>' << std::endl;
}
const int vector_length = 7;
int _tmain(int argc, _TCHAR* argv[])
{
unordered_vector<vec3> vectors(vector_length);
for (int i = 0; i < vector_length; ++i)
{
vectors.push_back(vec3(0.1f*i, 0.2f*i, 0.3f*i));
}
for (const auto& i : vectors)
{
//Prints correctly
printlnvalue(i);
//Function that does same thing but doesn't print correctly.
println(i);
}
return 0;
}
Output
<0,0,0>
<8.68805E-044,2.66467E-038,4.12969E-039>
<0.1,0.2,0.3>
<8.68805E-044,2.66467E-038,4.12969E-039>
<0.2,0.4,0.6>
<8.68805E-044,2.66467E-038,4.12969E-039>
<0.3,0.6,0.9>
<8.68805E-044,2.66467E-038,4.12969E-039>
<0.4,0.8,1.2>
<8.68805E-044,2.66467E-038,4.12969E-039>
<0.5,1,1.5>
<8.68805E-044,2.66467E-038,4.12969E-039>
<0.6,1.2,1.8>
<8.68805E-044,2.66467E-038,4.12969E-039>
unordered_vector declaration
template<typename T>
class unordered_vector
{
private:
T* m_data;
size_t m_size;
size_t m_capacity;
public:
typedef unordered_vector_iterator<T> iterator;
typedef unordered_vector_iterator<T> const const_iterator;
typedef ptrdiff_t difference_type;
typedef size_t size_type;
typedef T value_type;
typedef T* pointer;
typedef T& reference;
size_t size(void) const;
size_t capacity(void) const;
bool empty(void) const;
void resize(size_t count);
void reserve(size_t count);
void push_back(const T& val);
const_iterator begin(void) const;
const_iterator end(void) const;
void shrink_to_fit(void);
void erase(size_t i);
void erase(const T& val);
void erase_all(const T& val);
T& operator[](size_t i);
T operator[](size_t i) const;
unordered_vector& operator=(const unordered_vector& copy);
unordered_vector& operator=(unordered_vector&& mov);
unordered_vector(void);
unordered_vector(size_t _size);
unordered_vector(unordered_vector& copy);
unordered_vector(unordered_vector&& mov);
~unordered_vector(void);
};
unordered_vector definition
template<typename T>
/*
Returns the number of active units within
the unordered_vector.
*/
size_t unordered_vector<T>::size(void) const
{
return m_size;
}
template<typename T>
/*
Returns the potential size of the vector
before it would have to resize.
*/
size_t unordered_vector<T>::capacity(void) const { return m_capacity; }
template<typename T>
/*
Returns true if no active units are within the vector. size() == 0
*/
bool unordered_vector<T>::empty(void) const
{
return m_size == 0;
}
template<typename T>
/*
This resizes the vector where anything between the parameter and
size is part of the capacity.
*/
void unordered_vector<T>::reserve(size_t count)
{
if (count > m_capacity)
{
//Set capacity to new value
m_capacity = count;
//Make new array
T* new_ptr = static_cast<T*>(malloc(sizeof(T)*m_capacity));
//Copy preexisting data byte for byte
if (m_data != nullptr)
{
//Copy data from previous buffer to new buffer
memcpy(new_ptr, m_data, sizeof(T)*m_size);
//Delete previous buffer
free(m_data);
}
//Set pointer to the new array
m_data = new_ptr;
}
}
template<typename T>
/*
Will resize the vector but each of the units is
active if the buffer is increased. If the size
is reduced then items at the end are truncated.
*/
void unordered_vector<T>::resize(size_t count)
{
if (count > m_capacity)
{
//Set capacity to new value
m_capacity = count;
//Make new array
T* new_ptr = static_cast<T*>(malloc(sizeof(T)*m_capacity));
//Copy preexisting data byte for byte
if (m_data != nullptr)
{
//Copy data from previous buffer to new buffer
memcpy(new_ptr, m_data, sizeof(T)*m_size);
//Delete previous buffer
free(m_data);
}
//Set pointer to the new array
m_data = new_ptr;
//Make default values at each location
for (; m_size < count; ++m_size)
{
}
}
else if (count < m_capacity)
{
if (count < m_size) m_size = count;
//Set capacity to new value
m_capacity = count;
//Make new array
T* new_ptr = static_cast<T*>(malloc(sizeof(T)*m_capacity));
//Copy preexisting data byte for byte
if (m_data != nullptr)
{
//Copy data from previous buffer to new buffer
memcpy(new_ptr, m_data, sizeof(T)*m_size);
//Delete previous buffer
free(m_data);
}
//Set pointer to the new array
m_data = new_ptr;
}
}
template<typename T>
/*
Shrinks capacity so capacity() == size().
*/
void unordered_vector<T>::shrink_to_fit(void)
{
if (m_size != m_capacity && m_data != nullptr)
{
m_capacity = m_size;
//Make new array
T* new_ptr = static_cast<T*>(malloc(sizeof(T)*m_capacity));
//Copy preexisting data byte for byte
memcpy(new_ptr, m_data, sizeof(T)*m_size);
//Delete previous buffer
if (m_data != nullptr)
free(m_data);
//Set pointer to the new array
m_data = new_ptr;
}
}
template<typename T>
/*
Will copy a value into the next open space of the vector.
If there isn't room for an additional item it will resize
the vector to accomadate it.
*/
void unordered_vector<T>::push_back(const T& val)
{
//If we don't have any more room we need
//to resize the array
if (m_size == m_capacity)
{
m_capacity = m_capacity == 0 ? 8 : m_capacity * 2;
//Make new array
T* new_ptr = static_cast<T*>(malloc(sizeof(T)*m_capacity));
if (m_data != nullptr)
{
//Copy preexisting data byte for byte
memcpy(new_ptr, m_data, sizeof(T)*m_size);
//Delete previous buffer
free(m_data);
}
//Set pointer to the new array
m_data = new_ptr;
}
m_data[m_size++] = val;
}
template<typename T>
/*
Random accessor for writing to array.
*/
T& unordered_vector<T>::operator[](size_t i)
{
assert(i >= 0 && i < m_size);
return m_data[i];
}
template<typename T>
/*
Random accessor for reading from array.
*/
T unordered_vector<T>::operator[](size_t i) const
{
assert(i >= 0 && i < m_size);
return m_data[i];
}
template<typename T>
/*
Constant time erase. It reorders the vector
internally to allow this.
*/
void unordered_vector<T>::erase(size_t i)
{
assert(i >= 0 && i < m_size);
if (i == m_size - 1)
m_size -= 1;
else
{
m_data[i] = m_data[m_size - 1];
m_size -= 1;
}
}
template<typename T>
/*
Will remove the first value that matches
val if it exists. In the event no value is
found the request is ignored.
*/
void unordered_vector<T>::erase(const T& val)
{
for (size_t i = 0; i < m_size; ++i)
{
if (m_data[i] == val)
{
if (i == m_size - 1)
m_size -= 1;
else
{
m_data[i] = m_data[m_size - 1];
m_size -= 1;
}
break;
}
}
}
template<typename T>
/*
Will remove all values that match the parameter.
If no items match the parameter then the request
is ignored.
*/
void unordered_vector<T>::erase_all(const T& val)
{
for (size_t i = 0; i < m_size; ++i)
{
if (m_data[i] == val)
{
if (i == m_size - 1)
m_size -= 1;
else
{
m_data[i] = m_data[m_size - 1];
m_size -= 1;
//Haven't checked the back yet
//so we need to recheck i
i--;
}
}
}
}
template<typename T>
/*
Initializes the vector with 0 capacity.
*/
unordered_vector<T>::unordered_vector(void)
: m_data(nullptr), m_size(0), m_capacity(0) {}
template<typename T>
/*
Initializes the vector with a capacity() equal to
the parameter.
*/
unordered_vector<T>::unordered_vector(size_t count)
: m_data(nullptr), m_size(0), m_capacity(0)
{
reserve(count);
}
template<typename T>
/*
Will copy data from another unordered_vector.
*/
unordered_vector<T>::unordered_vector(unordered_vector<T>& vector)
{
if (vector.m_capacity > 0)
{
m_data = malloc(sizeof(T)*vector.m_capacity);
if (vector.m_size > 0)
memcpy(m_data, vector.m_data, sizeof(T)*vector.m_size);
}
m_capacity = vector.m_capacity;
m_size = vector.m_size;
}
template<typename T>
/*
Move constructor to effeciently transfer data between
a temporary and another unordered_vector.
*/
unordered_vector<T>::unordered_vector(unordered_vector<T>&& mov)
{
m_data = mov.m_data;
m_capacity = mov.m_capacity;
m_size = mov.m_size;
mov.m_data = nullptr;
}
template<typename T>
/*
Destructor that deallocates memory
if any was allocated. Will not deallocate
memory if move semantic was invoked.
*/
unordered_vector<T>::~unordered_vector(void)
{
if (m_data != nullptr)
{
free(m_data);
m_data = nullptr;
}
m_size = 0;
m_capacity = 0;
}
template<typename T>
unordered_vector<T>& unordered_vector<T>::operator=(const unordered_vector<T>& copy)
{
if (m_data != nullptr)
{
free(m_data);
m_data = nullptr;
}
if (copy.m_capacity > 0)
{
m_data = malloc(sizeof(T)*copy.capacity());
if (copy.m_size > 0)
memcpy(m_data, copy.m_data, sizeof(T)*copy.m_size)
}
m_capacity = copy.m_capacity;
m_size = copy.m_size;
return *this;
}
template<typename T>
unordered_vector<T>& unordered_vector<T>::operator=(unordered_vector<T>&& mov)
{
if (m_data != nullptr)
free(m_data);
m_data = mov.m_data;
m_capacity = mov.m_capacity;
m_size = mov.m_size;
mov.m_data = nullptr;
}
template<typename T>
unordered_vector_iterator<T> const unordered_vector<T>::begin(void) const
{
return unordered_vector_iterator<T>(this, 0);
}
template<typename T>
unordered_vector_iterator<T> const unordered_vector<T>::end(void) const
{
return unordered_vector_iterator<T>(this, m_size);
}
iterator declaration
template<typename T>
class unordered_vector_iterator
{
private:
const unordered_vector<T>* const m_container;
size_t m_index;
public:
unordered_vector_iterator& operator++(void);
unordered_vector_iterator& operator++(int);
unordered_vector_iterator& operator--(void);
unordered_vector_iterator& operator--(int);
T& operator*(void) const;
T& operator->(void) const;
bool operator==(const unordered_vector_iterator& iter) const;
bool operator!=(const unordered_vector_iterator& iter) const;
unordered_vector_iterator(const unordered_vector<T>* const container, size_t index)
: m_container(container), m_index(index) {}
};
iterator definition
template<typename T>
bool unordered_vector_iterator<T>::operator==(const unordered_vector_iterator<T>& iter) const
{
return iter.m_index == m_index && iter.m_container == m_container;
}
template<typename T>
bool unordered_vector_iterator<T>::operator!=(const unordered_vector_iterator<T>& iter) const
{
return iter.m_index != m_index || iter.m_container != m_container;
}
template<typename T>
unordered_vector_iterator<T>& unordered_vector_iterator<T>::operator++(void)
{
++m_index;
return *this;
}
template<typename T>
unordered_vector_iterator<T>& unordered_vector_iterator<T>::operator++(int)
{
++m_index;
return *this;
}
template<typename T>
unordered_vector_iterator<T>& unordered_vector_iterator<T>::operator--(void)
{
--m_index;
return *this;
}
template<typename T>
unordered_vector_iterator<T>& unordered_vector_iterator<T>::operator--(int)
{
--m_index;
return *this;
}
template<typename T>
T& unordered_vector_iterator<T>::operator*(void) const
{
return (*m_container)[m_index];
}
template<typename T>
T& unordered_vector_iterator<T>::operator->(void) const
{
return (*m_container)[m_index];
}
vec3 declaration
struct vec3
{
public:
float x, y, z;
//vector functions
void normalize(void);
float magnitude(void) const;
float magnitudeSq(void) const;
bool operator==(vec3 o) const;
bool operator!=(vec3 o) const;
//Constructors
vec3(float _x, float _y, float _z)
: x(_x), y(_y), z(_z) {}
vec3(float s)
: x(s), y(s), z(s) {}
vec3()
: x(0.f), y(0.f), z(0.f) {}
};
You might need to declare another operator in unordered_vector to return a const T&. That's how it's declared in the STL vector class:
typedef const_reference const T&;
const_reference operator[](size_t idx) const;
The issue with the code is that the const method does not return a reference value.
Also for the code to work it has to return a non-constant reference value for some reason. Not a great answer but it works flawlessly now by changing the const index operator to this.
template<typename T>
/*
Random accessor for reading from array.
*/
T& unordered_vector<T>::operator[](size_t i) const
{
assert(i >= 0 && i < m_size);
return m_data[i];
}
I have a technical problem and it's really confusing me. I apologise in advance because I may not be giving the relevant details; I don't yet why it's going wrong and it would be excessive to include all the code I'm working with.
I'm working with a large program that uses the C++ STL. I'm moving this code to a very sensitive environment without a standard clib nor STL implementaton; it will redefine malloc/free/new/delete etc... For that, I need to replace the std:: parts with my own simplified implementations. I've started with std::vector. Right now it's running in the standard ecosystem so it's the GNU libc and STL. The only thing that's changed is this vector class.
When I execute the program with the replaced class, it segfaults. I've put this through GDB and found that the program will request an object from the vector using the subscript operator. When the object reference is returned, a method is invoked and the program segfaults. It seems it can't find this method and ends up in main_arena() in GDB. The type of the object is an inherited class.
I'm really not sure at all what the problem is here. I would love to provide additional details, but I'm not sure what more I can give. I can only assume something is wrong with my vector implementation because nothing else in the program has been changed. Maybe there's something obvious that I'm doing wrong here that I'm not seeing at all.
I'm using: g++ (GCC) 4.4.5 20110214 (Red Hat 4.4.5-6)
I'd really appreciate any feedback/advice!
#ifndef _MYSTL_VECTOR_H_
#define _MYSTL_VECTOR_H_
#include <stdlib.h>
#include <assert.h>
typedef unsigned int uint;
namespace mystl
{
/******************
VECTOR
********************/
template <typename T>
class vector
{
private:
uint _size;
uint _reserved;
T *storage;
void init_vector(uint reserve)
{
if (reserve == 0)
{
_reserved = 0;
return;
}
storage = (T*)malloc(sizeof(T)*reserve);
assert(storage);
_reserved = reserve;
}
public:
vector()
{
// std::cerr << "default constructor " << this << std::endl;
storage = NULL;
_size = 0;
_reserved = 0;
}
vector(const vector<T> &other)
{
// std::cerr << "copy constructor " << this << std::endl;
storage = NULL;
_size = 0;
_reserved = 0;
init_vector(other.size());
_size = other.size();
for (uint i=0; i<other.size(); i++)
{
storage[i] = T(other[i]);
}
}
vector(uint init_num, const T& init_value)
{
// std::cerr << "special constructor1 " << this << std::endl;
storage = NULL;
_size = 0;
_reserved = 0;
init_vector(init_num);
for (size_t i=0; i<init_num; i++)
{
push_back(init_value);
}
}
vector(uint init_num)
{
// std::cerr << "special constructor2 " << this << std::endl;
storage = NULL;
_size = 0;
_reserved = 0;
init_vector(init_num);
}
void reserve(uint new_size)
{
if (new_size > _reserved)
{
storage = (T*)realloc(storage, sizeof(T)*new_size);
assert(storage);
_reserved = new_size;
}
}
void push_back(const T &item)
{
if (_size >= _reserved)
{
if (_reserved == 0) _reserved=1;
reserve(_reserved*2);
}
storage[_size] = T(item);
_size++;
}
uint size() const
{
return _size;
}
~vector()
{
if (_reserved)
{
free(storage);
storage = NULL;
_reserved = 0;
_size = 0;
}
}
// this is for read only
const T& operator[] (unsigned i) const
{
// do bounds check...
if (i >= _size || i < 0)
{
assert(false);
}
return storage[i];
}
T& operator[] (unsigned i)
{
// do bounds check...
if (i >= _size || i < 0)
{
assert(false);
}
return storage[i];
}
// overload = operator
const vector<T>& operator= (const vector<T>& x)
{
// check for self
if (this != &x)
{
_reserved = 0;
_size = 0;
storage = NULL;
init_vector( x.size() );
for(uint i=0; i<x.size(); i++)
{
storage[i] = T(x[i]);
}
_size = x.size();
}
return *this;
}
uint begin() const
{
return 0;
}
void insert(uint pos, const T& value)
{
push_back(value);
if (size() == 1)
{
return;
}
for (size_t i=size()-2; i>=pos&& i>=0 ; i--)
{
storage[i+1] = storage[i];
}
storage[pos] = value;
}
void erase(uint erase_index)
{
if (erase_index >= _size)
{
return;
}
//scoot everyone down by one
for (uint i=erase_index; i<_size; i++)
{
storage[i] = storage[i+1];
}
_size--;
}
void erase(uint start, uint end)
{
if (start > end)
{
assert(false);
}
if (end > _size)
end = _size;
for (uint i=start; i<end; i++)
{
erase(start);
}
assert(false);
}
void clear()
{
erase(0,_size);
}
bool empty() const
{
return _size == 0;
}
}; //class vector
}
#endif // _MYSTL_VECTOR_H_
Wow!
Your assignment operator also leaks memory.
Becuause you are using malloc/release the constructor to your type T will will not be called and thus you can not use your vector for anything except the most trivial of objects.
Edit:
I am bit bored this morning: Try this
#include <stdlib.h> // For NULL
#include <new> // Because you need placement new
// Because you are avoiding std::
// An implementation of swap
template<typename T>
void swap(T& lhs,T& rhs)
{
T tmp = lhs;
lhs = rhs;
rhs = tmp;
}
template <typename T>
class vector
{
private:
unsigned int dataSize;
unsigned int reserved;
T* data;
public:
~vector()
{
for(unsigned int loop = 0; loop < dataSize; ++loop)
{
// Because we use placement new we must explicitly destroy all members.
data[loop].~T();
}
free(data);
}
vector()
: dataSize(0)
, reserved(10)
, data(NULL)
{
reserve(reserved);
}
vector(const vector<T> &other)
: dataSize(0)
, reserved(other.dataSize)
, data(NULL)
{
reserve(reserved);
dataSize = reserved;
for(unsigned int loop;loop < dataSize;++loop)
{
// Because we are using malloc/free
// We need to use placement new to add items to the data
// This way they are constructed in place
new (&data[loop]) T(other.data[loop]);
}
}
vector(unsigned int init_num)
: dataSize(0)
, reserved(init_num)
, data(NULL)
{
reserve(reserved);
dataSize = reserved;
for(unsigned int loop;loop < dataSize;++loop)
{
// See above
new (&data[loop]) T();
}
}
const vector<T>& operator= (vector<T> x)
{
// use copy and swap idiom.
// Note the pass by value to initiate the copy
swap(dataSize, x.dataSize);
swap(reserved, x.rserved);
swap(data, x.data);
return *this;
}
void reserve(unsigned int new_size)
{
if (new_size < reserved)
{ return;
}
T* newData = (T*)malloc(sizeof(T) * new_size);
if (!newData)
{ throw int(2);
}
for(unsigned int loop = 0; loop < dataSize; ++loop)
{
// Use placement new to copy the data
new (&newData[loop]) T(data[loop]);
}
swap(data, newData);
reserved = new_size;
for(unsigned int loop = 0; loop < dataSize; ++loop)
{
// Call the destructor on old data before freeing the container.
// Remember we just did a swap.
newData[loop].~T();
}
free(newData);
}
void push_back(const T &item)
{
if (dataSize == reserved)
{
reserve(reserved * 2);
}
// Place the item in the container
new (&data[dataSize++]) T(item);
}
unsigned int size() const {return dataSize;}
bool empty() const {return dataSize == 0;}
// Operator[] should NOT check the value of i
// Add a method called at() that does check i
const T& operator[] (unsigned i) const {return data[i];}
T& operator[] (unsigned i) {return data[i];}
void insert(unsigned int pos, const T& value)
{
if (pos >= dataSize) { throw int(1);}
if (dataSize == reserved)
{
reserve(reserved * 2);
}
// Move the last item (which needs to be constructed correctly)
if (dataSize != 0)
{
new (&data[dataSize]) T(data[dataSize-1]);
}
for(unsigned int loop = dataSize - 1; loop > pos; --loop)
{
data[loop] = data[loop-1];
}
++dataSize;
// All items have been moved up.
// Put value in its place
data[pos] = value;
}
void clear() { erase(0, dataSize);}
void erase(unsigned int erase_index) { erase(erase_index,erase_index+1);}
void erase(unsigned int start, unsigned int end) /* end NOT inclusive so => [start, end) */
{
if (end > dataSize)
{ end = dataSize;
}
if (start > end)
{ start = end;
}
unsigned int dst = start;
unsigned int src = end;
for(;(src < dataSize) && (dst < end);++dst, ++src)
{
// Move Elements down;
data[dst] = data[src];
}
unsigned int count = start - end;
for(;count != 0; --count)
{
// Remove old Elements
--dataSize;
// Remember we need to manually call the destructor
data[dataSize].~T();
}
}
unsigned int begin() const {return 0;}
}; //class vector
With your current memory handling, this vector would only work with plain old data types.
To handle all types, it must ensure that objects
are actually created (malloc doesn't do that),
destroyed (free doesn't do that),
and you can't reallocate memory with realloc, because complex objects are not guaranteed to remain valid if they are byte-wise copied to another location.
Looks like the answer can be found in your question: "When the object reference is returned, a method is invoked and the program segfaults. It seems it can't find this method and ends up in main_arena() in GDB. The type of the object is an inherited class."
You probably store base class instance T in the vector, but make push_back for the instance of the class inherited from T. In push_back {storage[_size] = T(item);} you cast (actually make copy constructor T:T(const T&)) item to T (this probably named 'type cut'), then get reference to T and invoke a method of the class inherited from T using virtual table of T where the method is not defined yet/abstract. Am I right?
To make it properly work you should put T* in the vector or shared_ptr/unique_ptr depending on the ownership terms you apply to vector elements.
Generally in vector you can store only POD (Plain Old Data) types.