I have tried to make my own list for C++.
I have this Class:
template <class T>
class List
{
private:
T *value, *valueHelper;
int valueSize;
public:
int size;
List()
{
valueSize = 2;
value = (T*)malloc(sizeof(T) * valueSize);
size = 0;
}
void Add(T val)
{
size++;
if (size > valueSize)
{
valueSize *= 2;
valueHelper = (T*)malloc(sizeof(T) * valueSize);
memcpy(valueHelper, value, sizeof(T) * (valueSize / 2));
free(value);
value = valueHelper;;
}
value[size - 1] = val;
}
T operator[](int P)
{
return value[P];
}
};
When I try to use it in the main it works fine to Int.
Buy to Struct it is doing problems:
struct Material
{
string materialName;
int faceNum;
int meshNum;
Material(): materialName(""), faceNum(0), meshNum(0){};
};
void main()
{
List <Material> myList = List<Material>();
myList.Add(Material());
}
I got runtime error in the class on the line:
value[size - 1] = val;
Why?
You have at least two errors in your code :
- you cannot use memcpy to move class memory from one place to another except in very few cases. a std::string is not one of these cases.
- When you call an operator= it needs that the receiver is well formed, and it means that it is construct.
You hit the second error, because the value[0] is never construct, when you call the operator=, it is filled with garbage, and most probably try to delete random pointer value.
I imagine you prefer to construct object only when it is need, just like std::vector ? So a better implementation would be :
template <class T>
class List {
int m_size;
int m_capacity;
T * m_elems;
public:
List() :
m_size(),
m_capacity( 2 ),
m_elems( (T*) malloc( sizeof(T) * m_capacity ) ) {
}
void Add( T const & val ) {
if ( m_size + 1 > m_capacity ) {
m_capacity *= 2;
T * elems = (T*) malloc( sizeof(T) * m_capacity );
for( int i = 0 ; i != m_size ) {
new ( elems + i ) T( m_elems[i] ); // copy constructor
( m_elems + i )->~T(); // manually call the destructor
}
free( m_elems );
m_elems = elems;
}
new( m_elems + m_size++ ) T( val );
}
T operator[](int P) {
assert( P < m_size );
return m_elems[P];
}
};
This is because i did not pay attention to a third error in your original code ! The operator[] need to return by reference, not by value.
T & operator[](int P) {
assert( P < m_size );
return m_elems[P];
}
You probably want the const version too
T const & operator[](int P) const {
assert( P < m_size );
return m_elems[P];
}
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 wrote a simple B-Tree, with Node is defined as:
class Node {
Node* parent = nullptr;
std::uint32_t index = 0;
std::uint32_t height = 1;
std::vector<T> key;
std::vector<unique_alloc_ptr<Node>> child;
// ... details ...
};
where unique_alloc_ptr is the unique_ptr using my custom allocator.
template <typename T>
using unique_alloc_ptr = std::unique_ptr<T, std::function<void(T*)>>;
template <typename T>
unique_alloc_ptr<T> make_unique_fixed(FixedAllocator<T>& alloc) {
T* ptr = alloc.allocate(1);
alloc.construct(ptr);
std::function<void(T*)> deleter;
auto deleter_ = [](T* p, FixedAllocator<T>& alloc) {
alloc.destroy(p);
alloc.deallocate(p, 1);
};
deleter = [&deleter_, &alloc](auto&& PH1) { return deleter_(std::forward<decltype(PH1)>(PH1), alloc); };
return std::unique_ptr<T, decltype(deleter)>(ptr, deleter);
}
The custom allocator uses the memory pool as:
template <typename T>
class FixedAllocator {
struct Chunk {
// details ...
unsigned char data_[blockSize_ * numBlocks_];
};
// details ...
std::vector<Chunk> chunks_;
and my B-Tree uses the memory pool as member variable:
class BTree {
class Node {
// ...
};
// details ...
FixedAllocator<Node> alloc;
unique_alloc_ptr<Node> root;
};
But this gives segfault. As I guess, double free is the problem.
When lifetime of BTree ends, FixedAllocator<Node> is destroyed,
and its internal buffer std::vector<Chunk> is also destroyed.
The problem is, unique_alloc_ptr<Node> is also destroyed as well,
calling destructor of std::vector<unique_alloc_ptr<Node>> child,
which uses FixedAllocator<Node> as internal memory pool,
so double free problem occurs.
How can I solve this problem?
EDIT: Detailed implementation of FixedAllocator
template <typename T>
class FixedAllocator {
struct Chunk {
static constexpr std::size_t blockSize_ = sizeof(T);
static constexpr unsigned char numBlocks_ = FixedAllocator::numBlocks_;
Chunk() {
unsigned char i = 0;
for (unsigned char * p = &data_[0]; i != numBlocks_; p += blockSize_) {
*p = ++i;
}
}
void* allocate() {
unsigned char* result = &data_[firstAvailableBlock_ * blockSize_];
firstAvailableBlock_ = *result;
--blocksAvailable_;
return result;
}
void deallocate(void* p) {
assert(p >= &data_[0]);
auto* toRelease = static_cast<unsigned char*>(p);
assert((toRelease - &data_[0]) % blockSize_ == 0);
*toRelease = firstAvailableBlock_;
firstAvailableBlock_ = static_cast<unsigned char>((toRelease - &data_[0]) / blockSize_);
assert(firstAvailableBlock_ == (toRelease - &data_[0]) / blockSize_);
++blocksAvailable_;
}
bool hasBlock(void* p, std::size_t chunkLength) const {
auto* pc = static_cast<unsigned char*>(p);
return (&data_[0] <= pc) && (pc < &data_[chunkLength]);
}
[[nodiscard]] bool hasAvailable() const {
return (blocksAvailable_ == numBlocks_);
}
[[nodiscard]] bool isFilled() const {
return !blocksAvailable_;
}
unsigned char data_[blockSize_ * numBlocks_];
unsigned char firstAvailableBlock_ = 0;
unsigned char blocksAvailable_ = numBlocks_;
};
private:
static constexpr std::size_t blockSize_ = sizeof(T);
static constexpr unsigned char numBlocks_ = std::numeric_limits<unsigned char>::max();
std::vector<Chunk> chunks_;
Chunk* allocChunk_ = nullptr;
Chunk* deallocChunk_ = nullptr;
Chunk* emptyChunk_ = nullptr;
public:
using value_type = T;
void* doAllocate() {
if (!allocChunk_ || !allocChunk_->blocksAvailable_) {
auto it = chunks_.begin();
for (; ; ++it) {
if (it == chunks_.end()) {
chunks_.emplace_back();
allocChunk_ = &chunks_.back();
deallocChunk_ = &chunks_.front();
break;
}
if (it->blocksAvailable_) {
allocChunk_ = &*it;
break;
}
}
}
assert(allocChunk_);
assert(allocChunk_->blocksAvailable_);
return allocChunk_->allocate();
}
value_type* allocate(std::size_t n) {
assert(n == 1);
auto* p = static_cast<value_type*>(doAllocate());
return p;
}
Chunk* findVicinity(void* p) const {
assert(!chunks_.empty() && deallocChunk_);
const std::size_t chunkLength = numBlocks_ * blockSize_;
// bidirectional search
Chunk* lo = deallocChunk_;
Chunk* hi = deallocChunk_ + 1;
const Chunk* lbound = &chunks_.front();
const Chunk* hbound = &chunks_.back() + 1;
if (hi == hbound) {
hi = nullptr;
}
for (;;) {
if (lo) {
if (lo->hasBlock(p, chunkLength)) {
return lo;
}
if (lo == lbound) {
lo = nullptr;
if (!hi) {
break;
}
} else {
--lo;
}
}
if (hi) {
if (hi->hasBlock(p, chunkLength)) {
return hi;
}
if (++hi == hbound) {
hi = nullptr;
if (!lo) {
break;
}
}
}
}
return nullptr;
}
void deallocate(void* p, std::size_t n) noexcept {
assert(n == 1);
assert(!chunks_.empty());
assert(&chunks_.front() <= deallocChunk_);
assert(&chunks_.back() >= deallocChunk_);
assert(&chunks_.front() <= allocChunk_);
assert(&chunks_.back() >= allocChunk_);
Chunk* foundChunk = nullptr;
const std::size_t chunkLength = numBlocks_ * blockSize_;
if (deallocChunk_->hasBlock(p, chunkLength)) {
foundChunk = deallocChunk_;
} else {
foundChunk = findVicinity(p);
}
assert(foundChunk && foundChunk->hasBlock(p, chunkLength));
deallocChunk_ = foundChunk;
// double free check
assert(emptyChunk_ != deallocChunk_);
assert(!deallocChunk_->hasAvailable());
assert(!emptyChunk_ || emptyChunk_->hasAvailable());
deallocChunk_->deallocate(p);
if (deallocChunk_->hasAvailable()) {
// only release chunk if there are 2 empty chunks.
if (emptyChunk_) {
// if last chunk is empty, just let deallocChunk_ points
// to empty chunk, and release the last.
// otherwise, swap two and release an empty chunk
Chunk* lastChunk = &chunks_.back();
if (lastChunk == deallocChunk_) {
deallocChunk_ = emptyChunk_;
} else if (lastChunk != emptyChunk_) {
std::swap(*emptyChunk_, *lastChunk);
}
assert(lastChunk->hasAvailable());
chunks_.pop_back();
if ((allocChunk_ == lastChunk) || (allocChunk_->isFilled())) {
allocChunk_ = deallocChunk_;
}
}
emptyChunk_ = deallocChunk_;
}
}
template <typename... Args>
void construct (value_type* p, Args&&... args) {
std::construct_at(p, std::forward<Args>(args)...);
}
void destroy(value_type* p) {
std::destroy_at(p);
}
};
Everything seems to be copying fine, but when I call array2.print(), it shows segmentation fault. What am I doing wrong?
#include <iostream>
#include <initializer_list>
template <typename T>
class DynamicArray
{
private:
const int GROWTH_FACTOR = 2;
const int INITIAL_CAPACITY = 5;
T *m_array;
int m_capacity; // Capacity of the array
int m_size; // Number of added elements
public:
DynamicArray(std::initializer_list<T> elements)
: m_size(elements.size())
, m_capacity(elements.size() * 2)
{
m_array = new T[m_capacity];
std::copy(elements.begin(), elements.end(), m_array);
}
DynamicArray()
: m_size(0)
, m_capacity(INITIAL_CAPACITY)
{
m_array = new T[m_capacity];
}
~DynamicArray()
{
delete[] m_array;
}
DynamicArray(const DynamicArray& other)
: GROWTH_FACTOR(other.GROWTH_FACTOR)
, INITIAL_CAPACITY(other.INITIAL_CAPACITY)
, m_capacity(other.m_capacity)
, m_size(other.m_size)
{
T *m_array = new T[m_capacity];
std::copy(other.m_array, other.m_array + m_size, m_array);
}
int size()
{
return m_size;
}
int capacity()
{
return m_capacity;
}
void resize()
{
int new_capacity = m_capacity * GROWTH_FACTOR;
m_capacity = new_capacity;
T *temp = new T[new_capacity];
std::copy(m_array, m_array + m_capacity, temp);
delete[] m_array;
m_array = temp;
}
void deleteAt(int pos)
{
for (int i = pos; i < m_size - 1; i++)
{
(*this)[i] = (*this)[i + 1];
}
m_size--;
}
void insertAt(T value, int pos)
{
if (m_capacity == m_size)
{
resize();
}
for (int i = m_size - 1; i >= pos; i--)
{
(*this)[i + 1] = (*this)[i];
}
m_size++;
(*this)[pos] = value;
}
void append(T value)
{
insertAt(value, m_size);
}
void print() {
for (int i = 0; i < m_size; i++)
{
std::cout << (*this)[i] << ", ";
}
std::cout << std::endl;
}
T& operator[](int index)
{
if (index < 0 || index > m_size - 1)
{
throw std::invalid_argument("Index out of range!");
}
return m_array[index];
}
};
int main()
{
DynamicArray<int> array = { 1, 2, 3, 4 };
DynamicArray<int> array2 = array;
array2.print();
return 0;
}
The error is here
T *m_array = new T[m_capacity];
It should be
m_array = new T[m_capacity];
By declaring a new variable called m_array you hid the class member variable that you wanted to assign to. The technical name for this is shadowing, a good compiler would warn you about this.
You redeclare m_array in the cctor, which shadows the class member.
I created a custom class and custom hash functions for an unordered_set. Each time I try to insert into that unordered_set, I get a memory error:
malloc: *** error for object 0x9000000000000000: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap: 6
It is imperative that I use an unordered_set.
This is my custom class:
template <class T>
class Seed {
private:
Point start;
int seed_size;
T** data;
Seed* seed_match;
T _value;
public:
Seed(int x, int y, int s): seed_size(s), data( new T*[s] ), _value( T() ) {
start = Point(x, y);
for ( int i = 0; i < seed_size; i++ )
data[i] = new T[seed_size];
for ( int i = 0; i < seed_size; i++ ) {
for ( int j = 0; j < seed_size; j++ ) data[i][j] = NULL;
}
seed_match = NULL;
}
~Seed() {
for ( int x = 0; x < seed_size; x++ ) {
delete [] data[x];
}
delete [] data;
}
void add(int x, int y, T color_val) {
assert( data[x][y] == NULL );
data[x][y] = color_val;
_value += color_val;
}
bool match ( const Seed &_match ) {
if ( seed_match == NULL ) {
seed_match = &_match;
return true;
}
else return false;
}
T get_color(int x, int y) const {
assert( x >= 0 );
assert( y >= 0 );
assert( x < seed_size );
assert( y < seed_size );
return data[x][y];
}
bool operator==( const Seed<T> &b ) {
for ( int x = 0; x < seed_size; x++ ) {
for ( int y = 0; y < seed_size; y++ ) {
if ( get_color(x, y) != b.get_color(x, y) ) return false;
}
}
return true;
}
int seed_value() const { return _value; }
};
These are my custom hash functions:
template <class T>
struct SeedEqualByValue {
public:
bool operator()(const Seed<T> & seed1, const Seed<T> & seed2) const {
if (seed1.seed_value() == seed2.seed_value())
return true;
else
return false;
}
};
template <class T>
struct SeedHashByValue {
public:
size_t operator()(const Seed<T> & s1) const {
return std::hash<int>()( s1.seed_value() );
}
};
In my main, I instantiate 3 instances of the Seed class into 3 variables and also instantiate an unordered_set that takes in Seed, with the hash function as my SeedHashByValue struct and my compare function as SeedEqualByValue struct.
After creating the unordered_map, anytime I insert a Seed object into the unordered_map, I get an malloc error and I am not sure how to fix this.
These are the contents of my main function:
Seed<int> b(0, 0, 5);
Seed<int> a(0, 0, 5);
Seed<int> c(0, 0, 5);
c.add(4, 4, 100);
a.add(1, 2, 4);
a.add(1, 1, 3);
b.add(1, 1, 3);
unordered_set<Seed<int>, SeedHashByValue<int>, SeedEqualByValue<int> > seeds;
seeds.insert(c);
Also, Point is just a class that holds x and y values with public member variables int x and int y, in case anyone needed clarification.
To follow-up on what #Algirdas said, I believe what's happening is that the Seed is shallow-copied into the set, so you end up with a double delete on the same parent pointer, once when the set goes out of scope, and once when the variable goes out of scope.
You'll need to modify how you are handling the data by either transferring it in the assignment operator or by using something like a std::unique_ptr and making sure that it is assigned (which by default transfers ownership).
Here is my source.cpp:
#include "BST.h"
using namespace std;
int main(){
BST<int> test1;
BST<int> test2;
test1.insert(10);
test1.insert(15);
test1.insert(12);
test1.insert(14);
test1.insert(19);
test1.test();
cout << test1.contain(1) << endl;
cout << test1.isEmpty() << endl;
//cout << test2.isEmpty() << endl;
cin.get();
return 0;
}
And here is my header file:
//Haris
#include <iostream>
#include "Vector.h"
template <typename Comparable>
class BST
{
public:
int size = 0;
int currentRootindex = 0;
BST(){
for (int i = 0; i < 1000; i++){
data[i] = NULL;
}
}
~BST(){
}
void insert(Comparable obj){
if (data[0] == NULL){
data[0] = obj;
size++;
}
else{
for (int index = 0; index < size; index++){
if (data[currentRootindex] < obj && data[(2 * currentRootindex) + 2] == NULL){
data[(2 * currentRootindex) + 2] = obj;
size++;
break;
}
else if (data[currentRootindex] >= obj && data[(2 * currentRootindex) + 1] == NULL){
data[(2 * currentRootindex) + 1] = obj;
size++;
break;
}
else if (data[currentRootindex] < obj){
currentRootindex = ((2 * currentRootindex) + 2);
}
else{
currentRootindex = ((2 * currentRootindex) + 1);
}
}
}
currentRootindex = 0;
}
bool isEmpty(){
if (data[0] == NULL){
return true;
}
else
return false;
}
bool contain(Comparable obj){
for (int index = 0; index < size; index++){
if (data[currentRootindex] < obj){
currentRootindex = ((2 * currentRootindex) + 2);
}
else if (data[currentRootindex] > obj){
currentRootindex = ((2 * currentRootindex) + 1);
}
else
return true;
}
return false;
}
void test(){
cout << data[5] << endl;
}
private:
Vector<Comparable> data;
};
if I comment out the BST<int>test2 from source.cpp, the code works as intended.
But when i instantiate BST<int>test2 after BST<int>test1, I get Application Error: The memory could not be read. I have not declared any static variables, so none of the variables is being shared across different objects.
Also, the Vector header file, I am using is
#ifndef VECTOR_H
#define VECTOR_H
#include <algorithm>
#include <iostream>
template <typename Object>
class Vector
{
public:
explicit Vector( int initSize = 0 )
: theSize{ initSize }, theCapacity{ initSize + SPARE_CAPACITY }
{ objects = new Object[ theCapacity ]; }
Vector( const Vector & rhs )
: theSize{ rhs.theSize }, theCapacity{ rhs.theCapacity }, objects{ nullptr }
{
objects = new Object[ theCapacity ];
for( int k = 0; k < theSize; ++k )
objects[ k ] = rhs.objects[ k ];
}
Vector & operator= ( const Vector & rhs )
{
Vector copy = rhs;
std::swap( *this, copy );
return *this;
}
~Vector( )
{ delete [ ] objects; }
Vector( Vector && rhs )
: theSize{ rhs.theSize }, theCapacity{ rhs.theCapacity }, objects{ rhs.objects }
{
rhs.objects = nullptr;
rhs.theSize = 0;
rhs.theCapacity = 0;
}
Vector & operator= ( Vector && rhs )
{
std::swap( theSize, rhs.theSize );
std::swap( theCapacity, rhs.theCapacity );
std::swap( objects, rhs.objects );
return *this;
}
bool empty( ) const
{ return size( ) == 0; }
int size( ) const
{ return theSize; }
int capacity( ) const
{ return theCapacity; }
Object & operator[]( int index )
{
return objects[ index ];
}
const Object & operator[]( int index ) const
{
return objects[ index ];
}
void resize( int newSize )
{
if( newSize > theCapacity )
reserve( newSize * 2 );
theSize = newSize;
}
void reserve( int newCapacity )
{
if( newCapacity < theSize )
return;
Object *newArray = new Object[ newCapacity ];
for( int k = 0; k < theSize; ++k )
newArray[ k ] = std::move( objects[ k ] );
theCapacity = newCapacity;
std::swap( objects, newArray );
delete [ ] newArray;
}
// Stacky stuff
void push_back( const Object & x )
{
if( theSize == theCapacity )
reserve( 2 * theCapacity + 1 );
objects[ theSize++ ] = x;
}
// Stacky stuff
void push_back( Object && x )
{
if( theSize == theCapacity )
reserve( 2 * theCapacity + 1 );
objects[ theSize++ ] = std::move( x );
}
void pop_back( )
{
--theSize;
}
const Object & back ( ) const
{
return objects[ theSize - 1 ];
}
// Iterator stuff: not bounds checked
typedef Object * iterator;
typedef const Object * const_iterator;
iterator begin( )
{ return &objects[ 0 ]; }
const_iterator begin( ) const
{ return &objects[ 0 ]; }
iterator end( )
{ return &objects[ size( ) ]; }
const_iterator end( ) const
{ return &objects[ size( ) ]; }
static const int SPARE_CAPACITY = 2;
private:
int theSize;
int theCapacity;
Object * objects;
};
#endif
You're accessing out of bounds indices of the data vector. The default vector constructor initialises with size 0 (although it allocates 2 extra elements) but then you immediately try to access up to index 999:
BST(){
for (int i = 0; i < 1000; i++){
data[i] = NULL;
}
}
The second object isn't really related to the problem. Even with only one object, the code is still accessing memory it shouldn't.
Changing the data vector to initialise with size 1000 fixes the out of bounds errors for this particular code, but I suspect the BST class will cause more of these errors in future because I can't see any checks against the data vector's size.