Suppose we have a templated c++ list class. Yes, vector exists, but the point is to know how to handle this problem.
The Constructor allocates a block of n objects of type T but does not initialize because they are not used yet.
In the add method, we wish to copy in a new object, but using operator = is not possible because operator = would first destroy the existing object, which was never initialized. How does one copy in an object into data[used] ?
#include <string>
template<typename T>
class DynArray {
private:
int capacity;
int used;
T* data;
public:
DynArray(int initialCap) : capacity(initialCap), used(0), data((T*)new char[sizeof(T)*capacity]) {}
void add(const T& e) {
//TODO: if the dynarray is full, grow
data[used++] = e; //ERROR! Should use copy constructor!!!
}
};
int main() {
DynArray<std::string> a(5);
a.add(std::string("abc"));
}
You should use placement new:
void add(const T& e) {
//TODO: if the dynarray is full, grow
new (data + used) T(e);
used++;
}
Placement new constructs an object in already allocated memory.
For what you are attempting to do, you need to call T's copy constructor using placement-new. And don't forget to implement the Rule of 3/5/0 as well:
template<typename T>
class DynArray {
private:
int capacity;
int used;
T* data;
public:
DynArray(int initialCap = 0) : capacity(0), used(0), data(0) {
reserve(initialCap);
}
DynArray(const DynArray &src) : capacity(0), used(0), data(0) {
reserve(src.capacity);
for(int i = 0; i < src.used; ++i) {
add(src.data[i]);
}
}
// C++11 and higher only...
DynArray(DynArray &&src) : capacity(src.capacity), used(src.used), data(src.data) {
src.capacity = src.used = 0;
src.data = 0;
}
~DynArray() {
clear();
delete[] reinterpret_cast<char*>(data);
}
DynArray& operator=(const DynArray &rhs) {
if (&rhs != this) {
DynArray(rhs).swap(*this);
}
return *this;
}
// C++11 and higher only...
DynArray& operator=(DynArray &&rhs) {
DynArray(std::move(rhs)).swap(*this);
return *this;
}
void swap(DynArray &other) {
std::swap(data, other.data);
std::swap(used, other.used);
std::swap(capacity, other.capacity);
}
void clear() {
resize(0);
}
void reserve(int newCap) {
// TODO: round up newCap to an even block size...
if (newCap <= capacity) return;
T *newData = reinterpret_cast<T*>(new char[sizeof(T) * newCap]);
for(int i = 0; i < used; ++i) {
new (newData + i) T(data[i]);
}
delete[] reinterpret_cast<char*>(data);
data = newData;
capacity = newCap;
}
void resize(int newSize) {
if (newSize < 0) newSize = 0;
if (newSize == used) return;
if (newSize > used) {
reserve(newSize);
for(int i = used; i < newSize; ++i) {
new (data + i) T();
++used;
}
}
else {
for(int i = used-1; i >= newSize; --i) {
data[i]->~T();
--used;
}
}
}
void add(const T& e) {
if (used == capacity) {
reserve(capacity * 1.5);
}
new (data + used) T(e);
++used;
}
};
#include <string>
int main() {
DynArray<std::string> a(5);
a.add("abc");
}
The DynArray class has a type of T, so you should simply allocate an array of type T with size of initialCap, which is simply
new T[initialCap];
For built-in types, e.g. int, the elements are left uninitialized.
For others, like string, the default constructor of T is called to initialize the elements.
In the add method, we wish to copy in a new object, but using operator = is not possible because operator = would first destroy the existing object
data[used++] = e; This is perfectly fine. It assigns e to data[used] - calls the assignment operator of the string, and it won't cause any troubles. However, when your array grows, you would need to allocate new arrays with double capacity, copy over the elements, and destroy the old data.
Related
So I tried to make a custom string & vector class (from a youtube channel named The Cherno). But when I tried to make a copy constructor on that vector class with my custom string class, I get this exception: str was nullptr (Occured in the string copy constructor) and also when I tried with a primitive data types (int) I also get this exception: std::move<int & __ptr64>(...) returned nullptr (Occured on the re_allocate function).
main.cpp:
int main() {
utils::list<int> list;
list.place(5);
utils::list<int> other = list; // Works fine if I remove this line
}
String copy constructor:
utils::string::string(const string& other)
: pr_Length(other.pr_Length)
{
// If the other string was not initialized then return
if (!other.pr_Init) {
pr_Init = false;
return;
}
// Allocates a new char*
pr_Data = new char[pr_Length + 1];
// Copy all the char* on other string and copy it here
std::memcpy(pr_Data, other.pr_Data, pr_Length + 1);
// This string is initialized
pr_Init = true;
}
Vector class:
template<typename T>
class list {
private:
T* pr_Array;
size_t pr_Size;
size_t pr_Cap;
public:
using ValType = T;
using Iterator = list_iterator<list<T>>;
list() {
re_allocate(2);
}
~list() {
destroy_all();
::operator delete(pr_Array, pr_Cap * sizeof(T));
}
list(const list& other)
: pr_Size(other.pr_Size), pr_Cap(other.pr_Cap)
{
re_allocate(pr_Cap);
}
void add(const T& elem) {
if (pr_Size >= pr_Cap) {
re_allocate(pr_Cap + 2);
}
new(&pr_Array[pr_Size]) T(std::move(elem));
pr_Size++;
}
void add(T&& elem) {
if (pr_Size >= pr_Cap) {
re_allocate(pr_Cap + 2);
}
new(&pr_Array[pr_Size]) T(std::move(elem));
pr_Size++;
}
template<typename...Args>
T& place(Args&&... args) {
if (pr_Size >= pr_Cap) {
re_allocate(pr_Cap + 2);
}
new(&pr_Array[pr_Size]) T(std::forward<Args>(args)...);
return pr_Array[pr_Size++];
}
void destroy_back() {
if (pr_Size == 0) {
return;
}
pr_Array[pr_Size].~T();
pr_Size--;
}
void destroy_all() {
for (size_t i = 0; i < pr_Size; i++) {
pr_Array[i].~T();
}
pr_Size = 0;
}
const T& operator[](size_t i) const {
return pr_Array[i];
}
T& operator[](size_t i) {
return pr_Array[i];
}
const size_t size() const {
return pr_Size;
}
size_t size() {
return pr_Size;
}
Iterator begin() {
return Iterator(pr_Array);
}
Iterator end() {
return Iterator(pr_Array + pr_Size);
}
private:
void re_allocate(size_t cap) {
T* newBlock = (T*)::operator new(cap * sizeof(T));
if (cap < pr_Size) {
pr_Size = cap;
}
for (size_t i = 0; i < pr_Size; i++) {
new(newBlock + i) T(std::move(pr_Array[i]));
}
for (size_t i = 0; i < pr_Size; i++) {
pr_Array[i].~T();
}
::operator delete(pr_Array, pr_Cap * sizeof(T));
pr_Array = newBlock;
pr_Cap = cap;
}
};
utils::list<int> list;
main() default-constructs an instance of this template. Let's inspect the template and its default constructor, then.
T* pr_Array;
size_t pr_Size;
size_t pr_Cap;
This template class declares these members.
list() {
The constructor does not have a member initialization section, and these three members do not have a default value, so all of these three class members are not initialized to anything. Their initial value is random garbage.
re_allocate(2);
The constructor then calls re_allocate(2).
if (cap < pr_Size) {
re_allocate, at this point, compares its parameter, cap, against pr_Size. But if you've been keeping careful notes, you will then note that pr_Size was not initialized to anything. From this point on, everything is undefined behavior.
There may or may not be additional instances of undefined behavior in this tempalte class, but given that this undefined behavior always occurs in the constructor, no further analysis is possible to determine that.
For example, pr_reallocate refers to pr_Array too. The current state of affairs is that pr_Array is also uninitialized and contains random garbage, so there's that as well. Whether or not this remains undefined behavior depends on how the initial occurence of undefined behavior gets fixed.
Then there's potential undefined behavior in the copy-constructor... But, one step at a time...
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.)
I'm trying to overload the [] operator in a templated dynamic array, however it doesn't seem to be doing anything?
I created a templated dynamic array for school, I've tried separating the overload to outside the class.
The DynArray.h
template <typename T>
class DynArray
{
public:
//The constructor initialises the size of 10 and m_Data to nullptr
DynArray(void)
{
m_AllocatedSize = 10;
m_Data = nullptr;
}
//deletes m_Data
~DynArray()
{
delete[] m_Data;
m_Data = nullptr;
}
T* operator [] (int index)
{
return m_Data[index];
}
//creates the array and sets all values to 0
T* CreateArray(void)
{
m_Data = new T[m_AllocatedSize];
m_UsedElements = 0;
for (int i = 0; i < m_AllocatedSize; ++i)
{
m_Data[i] = NULL;
}
return m_Data;
}
private:
bool Compare(T a, T b)
{
if (a > b)
return true;
return false;
}
T* m_Data;
T* m_newData;
int m_AllocatedSize;
int m_UsedElements;
};
Main.cpp
#include <iostream>
#include "DynArray.h"
int main()
{
DynArray<int>* myArray = new DynArray<int>;
//runs the create function
myArray->CreateArray();
int test = myArray[2];
delete myArray;
return 0;
}
I expected the overload to return in this case the int at m_Data[2], however it doesn't seem to overload the [] at all instead says no suitable conversion from DynArray<int> to int.
You are returning a pointer which is not what you want. You should do like this:
T& operator [] (const int& index)
{
return m_Data[index];
}
Also myArray is a pointer you have to dereference it before using.
int test = (*myArray)[2];
It's better to not to use pointer:
int main()// suggested by #user4581301
{
DynArray<int> myArray;
//runs the create function
myArray.CreateArray();
int test = myArray[2];
return 0;
}
There is no reason for using pointers here.
Instead of new and delete for dynamic allocation it is better to use smart pointers.
There is also one issue here, you are not chacking the range and what if theindex was for example a negative number.
I am trying (struggling) writing a generic vector class using std::unique_ptr. In my constructor I get this exception thrown:
Exception thrown: write access violation.
std::unique_ptr<int [0],std::default_delete<int [0]> >::operator[](...) returned nullptr.
This is the associated function:
template <class T>
Vector<T>::Vector(int n, const T &value) {
capacity = (n > initial_capacity) ? n : initial_capacity;
size = n;
for (int i = 0; i < n; i++) {
data[i] = value;
}
}
I also get an error here in the main.cpp file:
assert(nullVector.getCapacity() == 100);
I believe this is because I did not set the capacity in the std::unique_ptr if that is even possible.
Here is part of my header file:
#ifndef Vector_h
#define Vector_h
template <class T>
class Vector {
private:
static constexpr int initial_capacity = 100;
// Instance variables
int capacity = 0;
int size = 0;
std::unique_ptr<T[]> data = nullptr;
void deepCopy(const Vector<T> &source) {
capacity = source.size + initial_capacity;
for (int i = 0; i < source.size; i++) {
data[i] = source.data[i];
}
size = source.size;
}
void expandCapacity() {
auto oldData = std::move(data);
capacity *= 2;
for (int i = 0; i < size; i++) {
data[i] = oldData[i];
}
}
public:
// Constructors
Vector() = default; // empty constructor
Vector(int n, const T &value); // constructor
Vector(Vector<T> const &vec); // copy constructor
Vector<T>& operator=(Vector<T> const &rhs); // assignment operator
// Rule of 5
Vector(Vector<T> &&move) noexcept; // move constructor
Vector& operator=(Vector<T> &&move) noexcept; // move assignment operator
~Vector(); // destructor
// Overload operators
T& operator[](int index);
T const& operator[](int index) const;
bool operator==(const Vector<T>&) const;
//Vector<T>& operator+=(const Vector<T> &other) {
// Vector<T> newValue(size + other.size);
// std::copy(this->data, this->data + this->size, newValue.data);
// std::copy(other.data, other.data + other.size, newValue.data + this->size);
// newValue.swap(*this);
//}
friend Vector<T>& operator+(Vector<T> &source1, Vector<T> &source2) {
int n = source1.getSize() + source2.getSize();
Vector<T> newSource(n,0);
for (int i = 0; i < source1.size; i++) {
newSource[i] = source1[i];
}
for (int i = 0; i < source2.size; i++) {
newSource[i + source1.getSize()] = source2[i];
}
return newSource;
}
friend std::ostream& operator<<(std::ostream &str, Vector<T> &data) {
data.display(str);
return str;
}
// Member functions
void swap(Vector<T> &other) noexcept;
void display(std::ostream &str) const;
int getSize() const { return size; }
int getCapacity() const { return capacity; }
bool empty() const { return size == 0; }
void clear() { size = 0; }
T get(int index) const;
void set(int index, const T &value);
void set(int index, T &&value);
void insert(int index, const T &value);
void insert(int index, T &&value);
void remove(int index);
void push_back(const T &value);
void pop_back();
};
template <class T>
Vector<T>::Vector(int n, const T &value) {
capacity = (n > initial_capacity) ? n : initial_capacity;
size = n;
for (int i = 0; i < n; i++) {
data[i] = value;
}
}
Here is part of the main.cpp file:
#include <algorithm>
#include <initializer_list>
#include <iostream>
#include <cassert>
#include <ostream>
#include "Vector.h"
int main() {
///////////////////////////////////////////////////////////////////////
///////////////////////////// VECTOR //////////////////////////////////
///////////////////////////////////////////////////////////////////////
Vector<int> nullVector; // Declare an empty Vector
assert(nullVector.getSize() == 0); // Make sure its size is 0
assert(nullVector.empty()); // Make sure the vector is empty
assert(nullVector.getCapacity() == 100); // Make sure its capacity is greater than 0
}
There is no such thing as a "capacity" of a unique_ptr. All an std::unique_ptr does is it holds on to a dynamically allocated object. It does not allocate an object by itself. Use std::make_unique() or new to create an new object and assign to your unique_ptr to hold on to.
I don't see you allocating any memory anywhere in your code. Unless you do allocate memory for your vector somewhere in a piece of code you didn't show, your data will just point to nullptr and trying to dereference it will crash (or worse). At least your expandCapacity() method does not seem to allocate any memory…
You probably should have a look at some material to learn about unique_ptr and smart pointers in general. For example: How to declare std::unique_ptr and what is the use of it? or this.
Essentially, my question is as follows: assuming I've created an assignment operator for a class, is it against convention or frowned upon to have my copy constructor just be this = item?
Lets say I'm creating a templated class with only the following data:
private:
int _size;
ItemType* _array;
If my assignment operator is as follows:
template<class ItemType>
void obj<ItemType>::operator = (const obj & copyThis){
_size = copyThis.getSize();
_array = new ItemType[_size];
for (int i = 0; i < _size; i++){
//assuming getItemAt is a function that returns whatever is in given location of object's _array
_array[i] = copyThis.getItemAt(i);
}
}
Then would it be against convention/looked down upon/considered incorrect if my copy constructor was simply as follows?
template<class ItemType>
obj<ItemType>::obj(const obj & copyThis){
this = copyThis;
}
It is generally safe to call operator= in the copy constructor (as long as the operator= does not try to use the copy constructor as part of its logic).
However, your operator= is implemented wrong to begin with. It leaks memory, does not handle this being assigned to itself, and does not return a reference to this.
Try this instead:
template<class ItemType>
obj<ItemType>::obj(const obj & copyThis)
: _size(0), _array(0)
{
*this = copyThis;
}
template<class ItemType>
obj<ItemType>& obj<ItemType>::operator=(const obj<ItemType> ©This)
{
if (this != ©This)
{
int newSize = copyThis.getSize();
ItemType *newArray = new ItemType[newSize];
// consider using std::copy() instead:
//
// std::copy(copyThis._array, copyThis._array + newSize, newArray);
//
for (int i = 0; i < newSize; ++i) {
newArray[i] = copyThis.getItemAt(i);
}
delete[] _array;
_array = newArray;
_size = newSize;
}
return *this;
}
That being said, it is generally better to implement operator= using the copy constructor, not the other way around:
template<class ItemType>
obj<ItemType>::obj(const obj & copyThis)
: _size(copyThis.getSize()), _array(new ItemType[_size])
{
for (int i = 0; i < _size; ++i){
_array[i] = copyThis.getItemAt(i);
}
// or:
// std::copy(copyThis._array, copyThis._array + _size, _array);
}
template<class ItemType>
obj<ItemType>& obj<ItemType>::operator=(const obj<ItemType> ©This)
{
if (this != ©This)
{
obj<ItemType> tmp(copyThis);
std::swap(_array, tmp._array);
std::swap(_size, tmp._size);
}
return *this;
}
Which can be cleaned up a little if you add a swap method:
template<class ItemType>
obj<ItemType>::obj(const obj & copyThis)
: _size(copyThis.getSize()), _array(new ItemType[_size])
{
for (int i = 0; i < _size; ++i){
_array[i] = copyThis.getItemAt(i);
}
}
template<class ItemType>
void obj<ItemType>::swap(obj<ItemType> &swapThis)
{
std::swap(_array, swapThis._array);
std::swap(_size, swapThis._size);
}
template<class ItemType>
obj<ItemType>& obj<ItemType>::operator=(const obj<ItemType> ©This)
{
if (this != ©This) {
obj<ItemType>(copyThis).swap(*this);
}
return *this;
}
That being said, if you replace your manual array with a std::vector, then you don't need to manually implement a copy constructor or a copy assignment operator at all, the compiler-generated default ones will suffice (since std::vector already implements copy semantics).