I want to print the results of the work of my custom memory allocator for the STL map container. I want to get the memory allocation map printed.
I have a problem with get_allocator(). See the example.
The get_allocator() call gives the allocator for the initial pair<int,int>. It even creates it...
Is there a way to get the true allocator (the instance of Pool for RBtree) which gives the memory for the map's elements?
I am using gcc. Thanks.
#include <memory>
#include <map>
using namespace std;
class Pool {
//...
public:
Pool(unsigned n);
~Pool();
void* alloc();
void free(void*);
void print_mm(); //print pool map
//...
};
void* Pool::alloc() {
//...
}
//...
void Pool::print_mm() {
//...
}
template<class T> class Pool_alloc : public allocator<T> {
static Pool pool;
public:
template<class U> struct rebind {
typedef Pool_alloc<U> other;
};
template<class U> Pool_alloc(const Pool_alloc<U>&) {}
Pool_alloc() {}
T* allocate(size_t, void*);
void deallocate(T*, size_t);
void print_mm() {pool.print_mm();}
};
template<class T> Pool Pool_alloc<T>::pool(sizeof(T));
template<class T> T* Pool_alloc<T>::allocate(size_t n, void* = 0) {
//...
return p;
}
template<class T> void Pool_alloc<T>::deallocate(T* p, size_t n) {
//...
}
main() {
map<int, int, less<int>, Pool_alloc<pair<int, int> > > m;
m[144] = 12;
m.get_allocator().print_mm(); //doesn't work - it gives the wrong allocator :-(
}
And the next is the complete code of the example - its base is taken from the famous Bjarne Stroustrup's book. ;-)
#include <iostream>
#include <memory>
#include <map>
using namespace std;
class Pool {
struct Link {Link *next;};
struct Chunk {
static const unsigned size = 8192 - sizeof(Chunk*); //page boundary
Chunk *next;
char mem[size];
} *chunks;
Link *head; //pointer to first free link
Pool(Pool&); //disable
void operator=(Pool&); //disable
void grow();
public:
const unsigned int atomsize;
Pool(unsigned n); //n - number of atoms
~Pool();
void* alloc(); //for one atom
void free(void*);
void print_mm(); //print pool memory map
};
void* Pool::alloc() {
if (head == 0) grow();
Link *p = head;
head = p->next;
return p;
}
void Pool::free(void *b) {
Link *p = static_cast<Link*>(b);
p->next = head;
head = p;
}
Pool::Pool(unsigned sz): atomsize(sz < sizeof(Link*) ? sizeof(Link*) : sz) {
cout << "atom size = " << atomsize << " bytes\n";
head = 0;
chunks = 0;
}
Pool::~Pool() {
Chunk *p = chunks;
while (p) {
Chunk *q = p;
p = p->next;
delete q;
}
}
void Pool::grow() {
Chunk *p = new Chunk;
p->next = chunks;
chunks = p;
const unsigned noe = Chunk::size/atomsize;
char *start = p->mem, *last = start + (noe - 1)*atomsize;
for (char *p = start; p < last; p += atomsize)
((Link*)p)->next = (Link*)(p + atomsize);
((Link*)last)->next = 0;
head = (Link*)start;
}
void Pool::print_mm() {
cout << "The pool memory map\n";
///...
}
template<class T> class Pool_alloc : public allocator<T> {
static Pool pool; //static for STL
public:
template<class U> struct rebind {
typedef Pool_alloc<U> other;
};
template<class U> Pool_alloc(const Pool_alloc<U>&) {}
Pool_alloc() {}
T* allocate(size_t, void*);
void deallocate(T*, size_t);
static void print_mm() {pool.print_mm();}
};
template<class T> Pool Pool_alloc<T>::pool(sizeof(T));
template<class T> T* Pool_alloc<T>::allocate(size_t n, void* = 0) {
T* p;
if (n == 1)
p = static_cast<T*>(pool.alloc());
else
p = static_cast<T*>(allocator<T>::allocate(n)); //STL level
//p = static_cast<T*>(operator new (sizeof(T)*n)); //OS level
return p;
}
template<class T> void Pool_alloc<T>::deallocate(T* p, size_t n) {
if (n == 1)
pool.free(p);
else
allocator<T>::deallocate(p, n); //STL level
//operator delete(p); //OS level
}
main() {
map<int, int, less<int>, Pool_alloc<pair<int, int> > > m;
m.insert(pair<int,int>(7, 8));
for (int i(0); i < 200; ++i)
m[i*i] = 2*i;
m.erase(169);
m.erase(121);
m[5] = 88;
cout << m[7] << '-' << m[5] << '-' << m.size() << endl;
m.get_allocator().print_mm(); //doesn't work - it gives the wrong allocator :-(
}
Try this:
template<class T, Pool* pool> class Pool_alloc : public allocator<T> {
public:
template<class U> struct rebind {
typedef Pool_alloc<U, pool> other;
};
This requires your Pool support data of different sizes.
Alternatively, replace Pool with Pool<sizeof(T)>, maybe using the static method trick to allocate the singleton instance, then you can at least find the various sized allocators. (They could register themselves with a global pool of pools so you can find them later).
Your pool allocator can then be smart enough to use different size chunks for different sized data requested. Or not, as you want.
Related
I want an array of objects prefixed with size/capacity. My requirements are:
The array elements should to be constructed on demand, like std::vector.
This object itself will be shared (i.e., heap allocated), so using std::vector instead would imply 2 levels of indirection and 2 allocations, which I have to avoid.
Requirements up to this point are non-negotiable. Here's my sample to get the rough idea of what I'd do:
#include <cassert>
#include <cstdio>
#include <string>
template <class T>
struct Array {
private:
int size_;
int capacity_;
alignas(T) unsigned char data_[];
Array() = default;
public:
Array(Array const&) = delete;
Array& operator=(Array const&) = delete;
static auto newArr(int capacity) {
auto p = new unsigned char[sizeof(Array) + capacity * sizeof(T)];
auto pObj = new (p) Array;
pObj->size_ = 0;
pObj->capacity_ = capacity;
return pObj;
}
static auto deleteArr(Array* arr) {
if (!arr) return;
for (int i = 0; i != arr->size_; ++i) arr->get(i).~T();
arr->~Array();
delete[] reinterpret_cast<unsigned char*>(arr);
}
auto& get(int index) {
return reinterpret_cast<T&>(data_[index * sizeof(T)]);
}
auto push_back(T const& t) {
assert(size_ < capacity_);
new (&get(size_++)) T(t);
}
};
int main() {
auto arr = Array<std::string>::newArr(5);
for (int i = 0; i != 3; ++i) {
arr->push_back(std::to_string(i));
}
for (int i = 0; i != 3; ++i) {
std::printf("arr[%d] = %s\n", i, arr->get(i).c_str());
}
Array<std::string>::deleteArr(arr);
}
It uses a flexible-array-member, which is an extension and that's OK by me (works in GCC and clang? then it's OK). But it is not constexpr friendly because it necessarily uses:
placement new, not allowed in constexpr context for some reason, even though that's surely what allocators do. We can't replace it with an allocator because they don't support the flexible array member trick.
reinterpret_cast to access the elements as T and to free the memory at the end.
My question:
How do I satisfy the previously mentioned requirements and keep the class constexpr friendly?
Ultimately, what you're trying to do is create a contiguous series of objects in unformed memory that isn't defined by a single, valid C++ struct or by a C++ array of Ts. Constexpr allocations cannot do that.
You can allocate a byte array in constexpr code. But you cannot subsequently do any of the casting that normal C++ would require in order to partition this memory into a series of objects of different types. You can allocate storage suitable for an array of Array<T> objects. Or you can allocate storage suitable for an array of T objects. But std::allocator<T>::allocate will always return a T*. And constexpr code doesn't let you cast this pointer to some other, unrelated type.
And without being able to do this cast, you cannot later call std::construct_at<T>, since the template parameter T must match the pointer type you give it.
This is of course by design. Every constexpr allocation of type T must contain zero or more Ts. That's all it can contain.
Considering #NicolBolas's answer, this can't be done as is, but if you can afford an indirection, it can be done. You do separate allocations if the object is constructed at compile-time where performance concern doesn't exist, and do the single-allocation + reinterpret_cast trick if constructed at runtime:
#include <cassert>
#include <new>
#include <type_traits>
#include <memory>
template <class T>
struct ArrayData {
protected:
int size_ = 0;
int capacity_ = 0;
T *buffer;
};
template <class T>
struct alignas(std::max(alignof(T), alignof(ArrayData<T>))) Array
: ArrayData<T> {
private:
constexpr Array() = default;
using alloc = std::allocator<T>;
using alloc_traits = std::allocator_traits<alloc>;
public:
Array(Array const &) = delete;
Array &operator=(Array const &) = delete;
constexpr static auto newArr(int capacity) {
if (std::is_constant_evaluated()) {
auto arr = new Array<T>();
alloc a;
T *buffer = alloc_traits::allocate(a, capacity);
arr->capacity_ = capacity;
arr->buffer = buffer;
return arr;
} else {
auto p = new unsigned char[sizeof(Array) + capacity * sizeof(T)];
auto pObj = new (p) Array;
pObj->capacity_ = capacity;
pObj->buffer = std::launder(reinterpret_cast<T *>(pObj + 1));
return pObj;
}
}
constexpr static auto deleteArr(Array *arr) noexcept {
if (!arr) return;
auto p = arr->buffer;
for (int i = 0, size = arr->size_; i != size; ++i)
std::destroy_at(p + i);
if (std::is_constant_evaluated()) {
auto capacity = arr->capacity_;
delete arr;
alloc a;
alloc_traits::deallocate(a, p, capacity);
} else {
arr->~Array();
delete[] reinterpret_cast<unsigned char *>(arr);
}
}
constexpr auto &get(int index) { return this->buffer[index]; }
constexpr auto push_back(T const &t) {
assert(this->size_ < this->capacity_);
std::construct_at(this->buffer + this->size_++, t);
}
};
constexpr int test() {
auto const size = 10;
auto arr = Array<int>::newArr(size);
for (int i = 0; i != size; ++i) arr->push_back(i);
int sum = 0;
for (int i = 0; i != size; ++i) sum += arr->get(i);
Array<int>::deleteArr(arr);
return sum;
}
int main() {
int rt = test();
int constexpr ct = test();
return rt == ct;
}
Here's another approach. I've used unions to make it possible for subobjects of a single array object to have different types and different lifetimes. I'm not entirely sure if everything's strictly legal, but the major compilers don't complain.
I did need to have newArr return an object rather than pointer. If that's not an issue, it would probably make more sense to just give FlexArray an actual constructor and destructor.
#include <memory>
#include <cassert>
template <typename T>
class FlexArray
{
public:
FlexArray(const FlexArray&) = delete;
FlexArray& operator=(const FlexArray&) = delete;
static constexpr FlexArray newArr(unsigned int capacity);
constexpr void deleteArr();
constexpr unsigned int size() const;
constexpr unsigned int capacity() const;
constexpr T& get(unsigned int index);
constexpr void push_back(T const& obj);
private:
struct Header {
unsigned int size;
unsigned int capacity;
};
static constexpr auto T_per_node =
(sizeof(T) < sizeof(Header)) ? sizeof(Header)/sizeof(T) : 1U;
union MaybeT {
unsigned char dummy;
T obj;
constexpr MaybeT() {}
explicit constexpr MaybeT(const T& src) : obj(src) {}
constexpr ~MaybeT() {}
};
union U {
Header head;
MaybeT data[T_per_node];
constexpr U() : data{} {}
constexpr ~U() {}
};
U* nodes;
explicit constexpr FlexArray(U* n) : nodes(n) {}
};
template <typename T>
constexpr FlexArray<T> FlexArray<T>::newArr(unsigned int capacity)
{
auto new_nodes = new U[1 + (capacity + (T_per_node-1))/T_per_node];
new_nodes[0].head = {0, capacity};
return FlexArray{new_nodes};
}
template <typename T>
constexpr void FlexArray<T>::deleteArr()
{
unsigned int i = size();
while (i--) {
get(i).~T();
}
delete[] nodes;
nodes = nullptr;
}
template <typename T>
constexpr unsigned int FlexArray<T>::size() const
{
return nodes[0].head.size;
}
template <typename T>
constexpr unsigned int FlexArray<T>::capacity() const
{
return nodes[0].head.capacity;
}
template <typename T>
constexpr T& FlexArray<T>::get(unsigned int index)
{
return nodes[1 + index / T_per_node].data[index % T_per_node].obj;
}
template <typename T>
constexpr void FlexArray<T>::push_back(const T& obj)
{
assert(size() < capacity());
auto index = nodes[0].head.size++;
MaybeT *addr = nodes[1 + index / T_per_node].data + (index % T_per_node);
addr->~MaybeT();
std::construct_at(addr, obj);
}
#include <functional>
constexpr int test()
{
int a = -1;
int b = 2;
int c = 5;
auto arr = FlexArray<std::reference_wrapper<int>>::newArr(3);
arr.push_back(std::ref(a));
arr.push_back(std::ref(b));
arr.push_back(std::ref(c));
arr.get(1).get() = 7;
auto sum = arr.get(0) + b + arr.get(2);
arr.deleteArr();
return sum;
}
static_assert(test() == 11);
See it on godbolt.
Is there a way to move already initialized data into a std::vector?
Here's my own simple vec class:
template<typename T>
class vec final
{
std::unique_ptr<T[]> pValues;
size_t size = 0;
public:
std::span<T> span() const { return std::span<int>(pValues.get(), size); }
vec(T* p, size_t sz) : size(sz) { pValues.reset(p); }
};
As you can see, it will take ownership of the memory passed to it:
int main()
{
constexpr size_t count = 99;
auto orig = std::make_unique<int[]>(count);
{
std::span<int> orig_(orig.get(), count);
std::iota(orig_.begin(), orig_.end(), -1);
assert((orig_[0] == -1) && (orig_[1] == 0) && (orig_[98] == 97));
}
vec<int> nums(orig.release(), count);
auto nums_ = nums.span();
assert((nums_[0] == -1) && (nums_[1] == 0) && (nums_[98] == 97));
}
This all works "as desired," but I'd like to do something similar with std::vector; in particular, I do not want to copy the data into a std::vector (imagine count being significantly larger than 99).
In other words, I'd like to do the "copy around some pointers" that (usually) happens when I std::move one std::vector to another; but the source is my own pointer. As my sample code shows, it's "easy" enough to do, but I don't want my own vec.
When I'm done, I'd like to "traffic" in a std::vector because that way I can completely forget about memory management (and having do further extend my own vec class). Using a std::vector also works well with existing C++ code that can't be changed.
You can have a std::vector use already allocated memory by providing it with a custom allocator:
#include <limits>
#include <iostream>
#include <memory>
#include <vector>
#include <numeric>
#include <cassert>
// allocator adapter to use pre-allocated memory
template <typename T, typename A=std::allocator<T>>
class reuse_mem_allocator : public A {
typedef std::allocator_traits<A> a_t;
public:
typedef typename a_t::size_type size_type;
typedef typename a_t::pointer pointer;
template <typename U> struct rebind {
using other =
reuse_mem_allocator<
U, typename a_t::template rebind_alloc<U>
>;
};
// have to store a ptr to pre-allocated memory and num of elements
reuse_mem_allocator(T* p = nullptr, size_type n = 0) throw()
: p_(p)
, size_(n)
{ }
reuse_mem_allocator(const reuse_mem_allocator& rhs) throw()
: p_(rhs.p_)
, size_(rhs.size_)
{ }
// allocate but don't initialize num elements of type T
pointer allocate (size_type num, const void* = 0) {
// Unless, it is the first call, and
// it was constructed with pre-allocated memory.
if (size_ != 0) {
if (num == size_) {
// Then, don't allocate; return pre-allocated mem
size_ = 0; // but only once
return p_;
} else {
throw std::bad_alloc();
}
} else {
// allocate memory with global new
T* ret = (T*)(::operator new(num*sizeof(T)));
return ret;
}
}
// convert value initialization into default/new initialization
template <typename U>
void construct(U* ptr)
noexcept(std::is_nothrow_default_constructible<U>::value) {
::new(static_cast<void*>(ptr)) U;
}
template <typename U, typename...Args>
void construct(U* ptr, Args&&... args) {
a_t::construct(static_cast<A&>(*this),
ptr, std::forward<Args>(args)...);
}
private:
pointer p_;
size_type size_;
};
int main()
{
constexpr size_t count = 9;
auto orig = std::make_unique<int[]>(count);
std::iota(orig.get(), orig.get()+count, -1);
assert((orig[0] == -1) && (orig[1] == 0) && (orig[count-1] == count-2));
std::vector<int, reuse_mem_allocator<int>> num(count, reuse_mem_allocator(orig.release(), count));
for (auto e : num) {
std::cout << e << " ";
}
std::cout << "\n";
std::cout << "size: " << num.size() << "\n";
}
I compiled it with c++17. Here is the output:
-1 0 1 2 3 4 5 6 7
size: 9
The allocator adapter is based on the one from this answer.
I create a class Vector that contain two variables that are template variable, I am trying to build such a dictionary that tell mark of a specific student.
The problem is I am struggling dynamic allocating memory with template. I have to do this without map or STL help. Can you explain, how to allocate them properly.
#define DEF_CAPACITY 20
template <class U, class T>
class Vector {
protected:
T* _data;
U* _keys;
int _size; //size in use
int _capacity; //available capacity
public:
//constructors
Vector(int capacity = DEF_CAPACITY);
~Vector();
int getSize() const { return size; }
void insert(U key, T data);
T operator[](U key);
};
template<class U, class T>
inline Vector<U, T>::Vector(int capacity)
{
this->_capacity = capacity;
this->_size = 0;
}
template<class U, class T>
Vector<U, T>::~Vector()
{
if (_data)
delete[] _data;
if (_keys)
delete[] _keys;
}
template<class U, class T>
void Vector<U, T>::insert(U key, T data)
{
_keys[_size] = new U;
//Maybe I have to do something like that, but all options I test doesn't work
//keys[_size] = new U[Some size];
_keys[_size] = key;
_data[_size] = new T;
_data[_size] = data;
_size++;
}
template<class U, class T>
T Vector<U, T>::operator[](U key)
{
int index = 0;
for (int i = 0; i < size; i++)
if (_keys[i] == key)
index = i;
return _data[index];
}
int main() {
Vector<string, int> grades;
grades.insert("john", 90);
grades.insert("marc", 100);
grades.insert("ron", 87);
grades.insert("lynda", 95);
//...
grades.insert("Tome", 93);
//...
cout << grades["marc"] << endl;
cout << grades["lynda"] << endl;
cout << grades["john"] << endl;
return 0;
}
To allocate n elements of type T you use:
T* array = new T[n];
so to allocate space for your keys and values:
// be aware: this will call the constructor for each element!!!
_keys = new U[n];
_data = new T[n];
If your "Vector" has a fixed capacity, this should be done in your constructor:
template<class U, class T>
inline Vector<U, T>::Vector(int capacity)
: _data(new T[capacity]);
, _keys(new U[capacity]);
, _size(0);
, _capacity(capacity);
{
}
after that, you can insert key/value pairs like this:
template<class U, class T>
void Vector<U, T>::insert(const U& key, const T& data)
{
if(_size == _capacity)
throw std::out_of_range("no space left!!!");
_keys[_size] = key; // copy assignment
_data[_size] = data; // copy assignment
_size++;
}
This design will unnecessarily call constructors for unused keys and values.
You could also do this with malloc and free to prevent this, though I wouldn't recommend going that way and instead use the standard library.
I am trying to initiate an object with an array. Is there a way to do it with pointers or should i find another way to do this.
EDIT: I want to write this code with dynamic memory allocation, I know vector is better way to solve this.
#include <iostream>
template <class t>
class die {
private:
int sideCount;
t* valueOfSides;
public:
die(int side, t arr[]) {
sideCount = side;
valueOfSides = (t*)malloc(side * sizeof(t));
for (int counter; counter < side; counter++) {
valueOfSides[counter] = val[counter];
}
}
~die() {
free(valueOfSides);
}
};
int main() {
die<int> sixsided(6, {1,2,3,4,5,6});
}
The right ways to do this would be
std::vector<t> valueOfSides;
template<size_t len> die(t (&arr)[len])
: valueOfSides(std::begin(arr), std::end(arr))
{}
or
std::vector<t> valueOfSides;
die(std::initializer_list<t> arr) : valueOfSides(arr) {}
I think. Though really, the best answer is
std::vector<t> valueOfSides;
die(std::vector<t> arr) : valueOfSides(std::move(arr)) {}
One should never use raw pointers to own memory in C++, and virtually never use new or malloc. As it is, you have undefined behavior in your code because of misusing malloc.
If you're absolutely insane, or doing homework, it can be done with raw pointers, though I doubt I can get it entirely right without tests and a compiler.
template<class t>
class die {
private:
int sideCount;
t* valueOfSides;
public:
die(int side, t* arr) {
sideCount = 0;
std::size_t buffer_size = sizeof(t)*side;
char* buffer;
try {
buffer = new char[side];
valueOfSides = reinterpret_cast<t*>(buffer);
for(int i=0; i<side; i++) {
new(valueOfSides+i)t(arr[i]);
sideCount++;
}
} catch(...) {
for(int i=sideCount; i>=0; i--)
(valueOfSides+i)->~t();
delete[]buffer;
throw;
}
}
die& operator=(die&& rhs) {
sideCount = rhs.sideCount;
valueOfSides = rhs.valueOfSides;
rhs.valueOfSides = nullptr;
rhs.sideCount = 0;
return *this;
}
//die& operator=(const die& rhs) not shown because its super hard.
~die() {
for(int i=sideCount; i>=0; i--)
(valueOfSides+i)->~t();
delete[]reinterpret_cast<char*>(valueOfSides);
}
};
As we've said before, getting this stuff right is crazy hard. Use a std::vector.
Use std::vector.
#include <iostream>
#include <initalizer_list>
#include <vector>
template<class T>
class die {
public:
die() = default;
die(std::initializer_list<T> list)
: sides{list}
{ /* DO NOTHING */ }
private:
std::vector<T> sides{};
};
int main() {
die<int> sixsided({1,2,3,4,5,6});
}
One way you can do this, using more of a C technique, is a variable argument list:
#include <cstdarg>
#include <iostream>
template <class t>
class die {
private:
int sideCount;
t* valueOfSides;
public:
die(int side, ...) {
sideCount = side;
valueOfSides = new t[side];
va_list args;
va_start(args, side);
for (int counter = 0; counter < side; counter++) {
valueOfSides[counter] = va_arg(args, t);
}
va_end(args);
}
~die() {
delete[] valueOfSides;
}
};
int main() {
die<int> sixsided(6, 1,2,3,4,5,6);
}
Rather than passing an array, you're passing the parameters individually (i.e. no need for a temporary array) and using a va_list to access them.
Also, the calls to malloc and free were replaced with new and delete which is the C++ way of allocating and deallocating memory.
The C++ solution:
template <class t>
class die {
private:
int sideCount;
t* valueOfSides;
public:
die(int side, t arr[]) {
sideCount = side;
valueOfSides = new T[side]
for (int counter = 0; counter < side; counter++) { //always initialize variables
valueOfSides[i] = arr[i];
}
}
~die() {
delete[] valueOfSides;
}
};
int main() {
int arr[6] = { 1,2,3,4,5,6 };
die<int> sixsided(6, arr);
}
The new operator is like malloc and the delete and delete[] operators are like free. They are dynamic allocators.
C solution:
template <class t>
class die {
private:
int sideCount;
t* valueOfSides;
public:
die(int side, t arr[]) {
sideCount = side;
valueOfSides = (t*)malloc(side * sizeof(t));
for (int counter = 0; counter < side; counter++) { //always initialize variables
valueOfSides[i] = arr[i];
}
}
~die() {
free(valueOfSides);
}
};
int main() {
int arr[6] = { 1,2,3,4,5,6 };
die<int> sixsided(6, arr);
}
Note: in C the <iostream> header will not work, this is C++ only.
There are other containers, namely std::vector, that can work, but this is the solution for your answer.
I'm writing my own dynamic array class in C++ (similarly to std::vector), and I'm running into a problem when having a dynamic array containing dynamic arrays.
Basically when having an array of all data types (int, double, float, std::string etc.) there is no problem and all the functionalities of the class works great.
When the data type is another array though something messes up and there is an error raising in the end of the program (free(): double free detected in tcache 2)
All of the code:
DynamicArray.h:
#pragma once
#include <iostream>
namespace Utils
{
template <typename T>
class DynamicArray
{
private:
size_t array_length;
T* array;
public:
~DynamicArray();
DynamicArray();
DynamicArray(const int& initialLength);
void Print();
size_t GetLength() const;
void AddItem(const T& newItem);
// TODO: void AddItems(const T* newItemsArray);
void RemoveItem(int index);
T& GetItem(int index);
void SetItem(const int& index, const T& newValue);
T& operator [](int index) const;
void ResetArray(T resetValue);
};
}
#include "DynamicArray.cpp"
DynamicArray.cpp:
#include "DynamicArray.h"
template<typename T>
Utils::DynamicArray<T>::~DynamicArray()
{
std::cout << "before del" << this->array_length << "\n";
if (this->array_length > 0)
delete[] this->array;
std::cout << "after del\n";
}
template<typename T>
Utils::DynamicArray<T>::DynamicArray()
{
this->array_length = 0;
}
template<typename T>
Utils::DynamicArray<T>::DynamicArray(const int& initialLength)
{
this->array_length = initialLength;
T* new_array = new T[initialLength];
this->array = new_array;
}
template<typename T>
void Utils::DynamicArray<T>::Print()
{
for (size_t i = 0; i < this->array_length; i++)
std::cout << this->array[i] << std::endl;
}
template<typename T>
size_t Utils::DynamicArray<T>::GetLength() const
{
return this->array_length;
}
template<typename T>
void Utils::DynamicArray<T>::AddItem(const T& newItem)
{
T* new_array = new T[this->array_length + 1];
for (size_t i = 0; i < this->array_length; i++)
new_array[i] = this->array[i];
new_array[array_length] = newItem;
// Releasing the memory of array
if (this->array_length != 0)
{
delete[] this->array;
this->array = nullptr;
}
this->array_length += 1;
this->array = new_array;
}
template<typename T>
void Utils::DynamicArray<T>::RemoveItem(int index)
{
T* new_array = new T[this->array_length - 1];
int temp_index = 0;
for (size_t i = 0; i < this->array_length; i++)
{
if (i != index)
{
new_array[temp_index] = this->array[i];
temp_index++;
}
}
// Releasing the memory of array
delete[] this->array;
this->array = nullptr;
this->array_length -= 1;
this->array = new_array;
}
template <typename T>
T& Utils::DynamicArray<T>::GetItem(int index)
{
return this->array[index];
}
template<typename T>
T& Utils::DynamicArray<T>::operator[](int index) const
{
return this->array[index];
}
template <typename T>
void Utils::DynamicArray<T>::ResetArray(T resetValue)
{
for (int i = 0; i < this->array_length; i++)
this->array[i] = resetValue;
}
template <typename T>
void Utils::DynamicArray<T>::SetItem(const int& index,const T& newValue)
{
this->array[index] = newValue;
}
main function:
#include <iostream>
#include "DynamicArray.h"
int main()
{
Utils::DynamicArray<Utils::DynamicArray<double>> outputs;
Utils::DynamicArray<double> singleOutput;
singleOutput.AddItem(1);
singleOutput.AddItem(1);
outputs.AddItem(singleOutput);
}
Output given when running the program:
before del2
after del
before del1
before del2
free(): double free detected in tcache 2
Aborted (core dumped)
Any ideas? No matter what I tried nothing worked..
You failed to write proper copy constructor and assignment operators:
DynamicArray(DynamicArray const& rhs); // copy constructor
DynamicArray& operator=(DynamicArray const& rhs); // copy assignment
When you don't write these yourself, they are generated with shallow copy semantics. Since your class "owns" a pointer, if you shallow copy it, then two instances o DynamicArray both own the same pointner, and when one is destroyed, it destroys the data pointed to by the other. And when the other is destroyed, you get a double free.
To write these you need to allocate memory and do a full copy.
(You also would eventually want to write a move constructor, and move assignment operator.)
The element declared on the stack in main() is also copied into the other DynamicArray. The double free occurs when the stack of main is cleaned up: first delete is in the destructor of singleOutput, and the second delete is in the destructor of outputs, which holds an element that has the same pointer as singleOutput.
You also leave your "array" member uninitialized in the default constructor. That does not set it to zero, it leaves garbage in it. (Which could be zero, but might not be.)