The following code constitutes a MCVE, this reproduces the problem I want to ask about but it's not the real code. The real code is quite more complicated so that's why I wrote this for a demonstration of the problem.
The key feature I am looking for is to be able to grow a dynamically allocated array, please do not suggest using the stl because it's explicitly forbidden. This code is for educational purpose and thus there are restrictions.
#include <cstring>
#include <iostream>
class Value
{
public:
Value(int value = 0);
Value(const Value &value);
Value &operator =(const Value &other);
~Value();
operator int() {return *m_absurdPointer;}
private:
int *m_absurdPointer;
};
Value::Value(int value) :
m_absurdPointer(new int[1])
{
*m_absurdPointer = value;
}
Value::Value(const Value &value)
{
m_absurdPointer = new int[1];
memcpy(m_absurdPointer, value.m_absurdPointer, sizeof(*m_absurdPointer));
}
Value &Value::operator =(const Value &other)
{
m_absurdPointer = new int[1];
memcpy(m_absurdPointer, other.m_absurdPointer, sizeof(*m_absurdPointer));
return *this;
}
Value::~Value()
{
delete[] m_absurdPointer;
}
class ValueArray
{
public:
ValueArray();
~ValueArray();
void append(const Value &value);
void show() const;
private:
Value *m_array;
unsigned int m_capacity;
unsigned int m_length;
};
ValueArray::ValueArray() :
m_array(nullptr)
, m_capacity(0)
, m_length(0)
{
}
ValueArray::~ValueArray()
{
delete[] m_array;
}
void
ValueArray::append(const Value &value)
{
if (m_length >= m_capacity)
{
Value *newarray;
unsigned int unitSize;
unitSize = 1;
newarray = new Value[m_capacity + unitSize];
if ((m_capacity > 0) && (m_array != nullptr))
memcpy(newarray, m_array, m_capacity * sizeof(*m_array));
delete[] m_array;
m_array = newarray;
m_capacity += unitSize;
}
m_array[m_length++] = value;
}
void
ValueArray::show() const
{
for (size_t i = 0 ; i < m_length ; ++i)
std::cout << static_cast<int>(m_array[i]) << std::endl;
}
int
main(void)
{
ValueArray example;
for (int i = 0 ; i < 10 ; ++i)
example.append(Value(i));
example.show();
return 0;
}
It causes as you can see a double free issue, because the delete[] m_array; calls the destructor of the class Value after it has copied the values to the re-newed array.
I tried to do this with malloc()/realloc() but I need the destructor of Value() to be called so new is mandatory because I can't use free().
How to prevent this?, if I remove the delete[] m_absurdPointer; the double free would be gone of course but there would be a memory leak.
You basically want to implement an own vector class, right?
OK, first things first: As far as I know you cannot grow previously allocated memory. At least not with the standard allocator.
So you need to allocate a new, larger chunk of memory.
You can do this the standard way, using new:
Type * newdata = new Type[size];
In this case the constructor of the class Type will be called for each new element, which is size times.
To get your old data into that new array you need to copy or move it there:
for (size_t it = 0; it < oldsize; ++it) {
newdata[it] = olddata[it];
// newdata[it] = std::move(olddata[it]);
}
This is what std::copy resp. std::move are doing. (You could also use std::swap inside a loop.)
For that to work the Type class needs both a default constructor and a valid implementation of copy or move assignment.
You're using memcpy. In C++, this is generally a bad idea: Your implemented assignment operator isn't called, Therefore both the objects in your old array and the raw copies are using the same pointer, which is why you get that double free, obviously.
You could also allocate raw memory and use placement new to copy or move construct the new objects from the old ones:
void * memory = new char[size * sizeof(Type)];
for (size_t it = 0; it < oldsize; ++it) {
new (memory + it * sizeof(Type)) Type(olddata[it]); // copy
}
The above is only an example, for real code you need to consider alignment, too.
Finally, I'm sure you can somehow trick the default allocator to free your (old) memory without destructing the objects within, this allowing you to use the raw copy memcpy made. Though this would be a hack and could break on complex classes, it's not the C++ way of doing this.
The idiomatic way is to copy or move the old objects to the new storage (with either assignment or construction).
You should use the move-constructor if you have to stick with an vector-like implementation of ValueArray:
class Value
{
public:
Value(int value = 0);
Value(const Value &value);
Value(Value&& val);
Value &operator =(const Value &other);
Value &operator =(Value&& other);
~Value();
operator int() {return *m_absurdPointer;}
private:
int *m_absurdPointer;
};
Value::Value(Value&& o) : m_absurdPointer(o.m_absurdPointer) {
o.m_absurdPointer = nullptr;
}
Value &operator =(Value&& o) {
delete[] this->m_absurdPointer;
this->m_absurdPointer = o.m_absurdPointer;
o.m_absurdPointer = nullptr;
}
void
ValueArray::append(const Value &value)
{
if (m_length >= m_capacity)
{
Value *newarray;
unsigned int unitSize;
unitSize = 1;
newarray = new Value[m_capacity + unitSize];
if ((m_capacity > 0) && (m_array != nullptr)) {
std::move(m_array, m_array + m_length, newarray);
}
delete[] m_array;
m_array = newarray;
m_capacity += unitSize;
}
}
Related
I have written a DynamicArray class in the past analogous to vector which worked.
I have also written as a demo, one where the performance is bad because it has only length and pointer, and has to grow every time. Adding n elements is therefore O(n^2).
The purpose of this code was just to demonstrate placement new. The code works for types that do not use dynamic memory, but with string it crashes and -fsanitize=address shows that the memory allocated in the addEnd() method is being used in printing. I commented out removeEnd, the code is only adding elements, then printing them. I'm just not seeing the bug. can anyone identify what is wrong?
#include <iostream>
#include <string>
#include <memory.h>
using namespace std;
template<typename T>
class BadGrowArray {
private:
uint32_t size;
T* data;
public:
BadGrowArray() : size(0), data(nullptr) {}
~BadGrowArray() {
for (uint32_t i = 0; i < size; i++)
data[i].~T();
delete [] (char*)data;
}
BadGrowArray(const BadGrowArray& orig) : size(orig.size), data((T*)new char[orig.size*sizeof(T)]) {
for (int i = 0; i < size; i++)
new (data + i) T(orig.data[i]);
}
BadGrowArray& operator =(BadGrowArray copy) {
size = copy.size;
swap(data, copy.data);
return *this;
}
void* operator new(size_t sz, void* p) {
return p;
}
void addEnd(const T& v) {
char* old = (char*)data;
data = (T*)new char[(size+1)*sizeof(T)];
memcpy(data, old, size*sizeof(T));
new (data+size) T(v); // call copy constructor placing object at data[size]
size++;
delete [] (char*)old;
}
void removeEnd() {
const char* old = (char*)data;
size--;
data[size].~T();
data = (T*)new char[size*sizeof(T)];
memcpy(data, old, size*sizeof(T));
delete [] (char*)old;
}
friend ostream& operator <<(ostream& s, const BadGrowArray& list) {
for (int i = 0; i < list.size; i++)
s << list.data[i] << ' ';
return s;
}
};
class Elephant {
private:
string name;
public:
Elephant() : name("Fred") {}
Elephant(const string& name) {}
};
int main() {
BadGrowArray<int> a;
for (int i = 0; i < 10; i++)
a.addEnd(i);
for (int i = 0; i < 9; i++)
a.removeEnd();
// should have 0
cout << a << '\n';
BadGrowArray<string> b;
b.addEnd("hello");
string s[] = { "test", "this", "now" };
for (int i = 0; i < sizeof(s)/sizeof(string); i++)
b.addEnd(s[i]);
// b.removeEnd();
cout << b << '\n';
BadGrowArray<string> c = b; // test copy constructor
c.removeEnd();
c = b; // test operator =
}
The use of memcpy is valid only for trivially copyable types.
The compiler may even warn you on that, with something like:
warning: memcpy(data, old, size * sizeof(T));
writing to an object of non-trivially copyable type 'class string'
use copy-assignment or copy-initialization instead [-Wclass-memaccess]
Note that your code do not move the objects, but rather memcpy them, which means that if they have for example internal pointers that point to a position inside the object, then your mem-copied object will still point to the old location.
Trivially Copyable types wouldn't have internal pointers that point to a position in the object itself (or similar issues that may prevent mem-copying), otherwise the type must take care of them in copying and implement proper copy and assignemnt operations, which would make it non-trivially copyable.
To fix your addEnd method to do proper copying, for non-trivially copyable types, if you use C++17 you may add to your code an if-constexpr like this:
if constexpr(std::is_trivially_copyable_v<T>) {
memcpy(data, old, size * sizeof(T));
}
else {
for(std::size_t i = 0; i < size; ++i) {
new (data + i) T(std::move_if_noexcept(old[i]));
}
}
In case you are with C++14 or before, two versions of copying with SFINAE would be needed.
Note that other parts of the code may also require some fixes.
I have been learning C++ for some time and started writing a larger project just to realise that whatever I was thinking about C++ is wrong.
Basically I have a class called DenseVector which holds doubles. I want to move, copy construct etc that vector to use it with vectors etc.
The class looks something like this:
DenseVector.h:
class DenseVector {
private:
double* values = nullptr;
int size;
public:
DenseVector();
DenseVector(int size);
DenseVector(double x, double y);
DenseVector(double x, double y, double z);
DenseVector(const DenseVector &other);
DenseVector(DenseVector &&other);
virtual ~DenseVector();
DenseVector& operator=(const DenseVector& other);
DenseVector& operator=(DenseVector&& other);
}
DenseVector.cpp
DenseVector::DenseVector() : DenseVector(3) {}
DenseVector::DenseVector(int size) : size(size) {
this->values = new double[size + size % 2]{0};
}
DenseVector::DenseVector(double x, double y) {
this->size = 2;
this->values = new double[2]{x,y};
}
DenseVector::DenseVector(double x, double y, double z) {
this->size = 3;
this->values = new double[4]{x,y,z};
}
DenseVector::DenseVector(const DenseVector &other) : size(other.size) {
this->values = new double[size + size % 2]{0};
memcpy(values, other.values, size * sizeof(double));
}
DenseVector::DenseVector(DenseVector &&other) : values(other.values), size(other.size){}
DenseVector::~DenseVector() {
if(values != nullptr){
delete values;
values = nullptr;
}
}
DenseVector &DenseVector::operator=(const DenseVector &other) {
this->size = other.size;
memcpy(values, other.values, size * sizeof(double));
return *this;
}
DenseVector &DenseVector::operator=(DenseVector &&other) {
this->values = other.values;
this->size = other.size;
return *this;
}
I assume its a very straight forward implementation for mathematical vectors in C++. Note that size of the array internally is always a multiple of two. This is due to speedups using AVX/SSE which is not part of this question.
Basically I keep getting a Segmentation fault inside the deconstructor when trying to delete the value and I have no idea why this keeps on happening!
And example would be the following snippet:
std::vector<DenseVector> positions;
for(int i = 0; i < 10; i++){
positions.push_back({1,2,3});
}
This really confuses me and I would be very very happy if someone could help me with this problem as this had happened to me before many times in other programs.
Also what would be the difference between using push_back and emplace_back in this case? Should one prefer one over the other one? I do not understand at which point objects will be created, moved, deleted etc.
Greetings
Finn
I can see at least two problems:
DenseVector &DenseVector::operator=(const DenseVector &other) {
this->size = other.size;
memcpy(values, other.values, size * sizeof(double));
return *this;
}
You are not checking whether you have enough space in this->values. If this->size is smaller than other->size, you need to reallocate.
DenseVector::DenseVector(DenseVector &&other) : values(other.values),
size(other.size){}
DenseVector &DenseVector::operator=(DenseVector &&other) {
this->values = other.values;
this->size = other.size;
return *this;
}
In both cases you end up with two pointers pointing to the same memory. Now when you destroy both vectors, you get a double delete. You need to have other->values = nullptr; in both functions.
A better way to fix both issues it to use std::vector and rely on the rule of zero.
You have several problems.
The one in destructor is that delete should match with new not new[].
You have to use delete[] here:
DenseVector::~DenseVector() {
delete [] values;
}
Note: Deleting null pointer is fine, no test needed. setting values to nullptr is useless too, as reading the value after the object is destroyed is UB anyway.
Your move constructor doesn't move, but shallow copy, so you will have double delete, it should be
DenseVector::DenseVector(DenseVector &&other) /*noexcept*/: values(other.values), size(other.size)
{
other.vlalues = nullptr;
other.size = 0;
}
You have many problems:
first you should move objects in move-ctor and leave the moved-from in a valid state:
DenseVector::DenseVector(DenseVector &&other) : values(std::move(other.values)), size(std::move(other.size))
{
other.values = nullptr;
}
You should use ctor-init list to initialize member data rather than assign to them inside ctor body after being default-init in ctor-init-list.
DenseVector::DenseVector(int size) : size(size),
values(new double[size]{0})
{
}
Also in the destructor you are freeing an array of doubles with delete which is Undefined Behavior so use it this way:
delete[] values;
You don't need to check in the dtor whether values is nullptr or not:
delete[] values;
Condition
In lectures, we have already started to implement our vector. In this task, you need to develop it: add the Size, Capacity, and PushBack methods. Send the simple_vector.h header file containing the SimpleVector class template declaration and definition for verification:
Requirements:
the Capacity method should return the current capacity of the vector — the number of elements that fit into the memory block currently allocated by the vector
the Size method must return the number of elements in the vector
the PushBack method adds a new element to the end of the vector; if there is no free space left in the current allocated memory block (i.e. Size() == Capacity()), the vector must allocate a block of size 2 * Capacity(), copy all the elements to it, and delete the old one.
the first call to the PushBack method for a newly created object must make the capacity equal to one
the Push Back method must have a amortized constant complexity
the begin and end methods must return iterators the current beginning and end of the vector
the current memory block allocated by the vector must be freed in the destructor
also see the attached solution template for additional requirements for working with SimpleVector in unit tests.
The preparation of the decision:
simple_vector.h: https://d3c33hcgiwev3.cloudfront.net/q-OL4qX_EeilzRLZf2WxfA_ac4e8270a5ff11e89fd0455a8819d387_simple_vector.h?Expires=1596067200&Signature=cLfBpytTripoqpOYaW9g4~2-JqTI~8HtxahNwNATwBeq28RdXCvkcqghN~UUPv~wx1XZTVOTs8JDsZQjEALk6Soy70QFADkK9lSfFpLNcQq-Dxd4oxk-C5QDEhadM1LrVGe8Rmz0jRYgIV5sDTvAATBhiY3k-KqbAaDe1AK6QiE_&Key-Pair-Id=APKAJLTNE6QMUY6HBC5A
simple_vector.cpp: https://d3c33hcgiwev3.cloudfront.net/uoPvEoauEeianAr0yIdmDg_bae6cec086ae11e88d9327752d64e780_simple_vector.cpp?Expires=1596067200&Signature=CE1Mox1yU6LjGDXL1xstxT9anv9NI~otNwhBw5AbPyLBquRIi9E6cotR~BQsrvU-djoksfjV9YgnsyF00eFnVjsk~oF0z18wkVkgdIirPB-NNLH0aFvD4WFG97qmSuD0WjeetWyi6UR5BKYCnwfO~ax6-HZLM-GWheO9LHc~BvE_&Key-Pair-Id=APKAJLTNE6QMUY6HBC5A
Comment:
The header file that you send for verification should not include the <vector>, <list>, <forward_list>, <deque>, <map> files. If you have one of these files enabled, you will get a compilation error.
Hint:
For sure, your implementation of the SimpleVector class template will have a field that is a pointer. In the default constructor, you will need to initialize it with something. In the lectures, we only discussed one way to initialize pointers — using the new operator. In C++, there is a special value that means a pointer that points to nothing — nullptr:
int* p = nullptr;
string* q = nullptr;
map<string, vector<int>>* r = nullptr;
You can use nullptr to initialize the pointer in the default constructor.
How to send:
When the work is ready, you can upload files for each part of the task on the 'My work'tab.
And here is my .hsolution to which the Coursera testing system responds a 10 != 8: Memory leak detected. However I can't figure out where the leak is going. Help me pls.
#pragma once
#include <cstdlib>
using namespace std;
template <typename T>
class SimpleVector {
public:
SimpleVector()
: data(nullptr)
, end_(data)
, size_(0) {}
explicit SimpleVector(size_t size)
: data(new T[size])
, end_(data + size)
, size_(size) {}
~SimpleVector() {
delete[] data;
}
T& operator[](size_t index) { return data[index]; }
T* begin() const { return data; }
T* end() const { return end_; }
size_t Capacity() const { return end_ - data; }
size_t Size() const { return size_; }
void PushBack(const T& value) {
if (size_ == Capacity()) {
if (size_ == 0) {
delete[] data;
data = new T[1];
data[size_] = value;
++size_;
end_ = data + size_;
}
else {
T* local_data = new T[size_];
for (size_t i = 0; i < size_; ++i) {
local_data[i] = data[i];
}
delete[] data;
data = new T[2 * Capacity()];
for (size_t i =0; i < size_; ++i) {
data[i] = local_data[i];
}
delete[] local_data;
data[size_] = value;
++size_;
end_ = data + size_ * 2;
}
}
else {
data[size_] = value;
size_++;
}
}
private:
T *data;
T *end_;
size_t size_;
};
Thank you in advance.
There is a memory leak in PushBack due to lack of exception safety. Consider:
T* local_data = new T[size_];
// potentially throwing operations here...
delete[] local_data;
If those operations throw, then delete[] local_data; will never be executed.
Typical way to avoid such memory leak is to use smart pointers instead of bare pointers for ownership. The antiquated way is to use try-catch.
Your class also fails to enforce the class invariant of uniqueness of data pointer. Such constraint is essential for the destructor to be correct, because an allocation must be deleted exactly once, and no more.
Making a copy of an instance of the class will result in undefined behaviour because of same pointer being deleted in multiple destructors. Another consequence is that the assigment operators will leak the previously allocated memory (before the UB occurs in the destructor):
{
SimpleVector vec(42);
SimpleVector another(1337);
SimpleVector vec = another; // memory leak in assignment operator
} // undefined behaviour in the destructor
The problem is in the copy and move constructors and assignment operators, which you've left as implicitly generated. The implicitly generated special member functions will copy the pointer value, violating its uniqueness (and failing to delete the previous allocation in case of assignment). In other words, those functions perform a shallow copy.
Using a smart pointer as the member is an easy solution. Otherwise, you must implement copy and move constructors and assignment operators that don't leak, nor violate uniqueness.
Note that even if you did use a smart pointer, you'd still need user defined copy etc. because of the end pointer. If you instead used an integer that is relative to data, then you could avoid defining those functions.
P.S. There is no need to allocate twice, and copy twice. Instead, allocate one larger buffer, copy the old one, delete the old, point to the new.
P.P.S. As a sidenote: The vector you are implementing behaves quite differently from the standard vector, which is probably intentional by your teacher. When I add an object to a vector of 10 elements, I would expect only one element to be created and possibly 10 be copied due to relocation, rather than 20 objects being created with 9 being unaccessible.
A proper implementation of vector separates the allocation of memory, and creation of objects into that memory which allows the growth of the memory to be geometric without creating objects until they are added into the vector. I suspect that how to do this is outside the scope of your exercise.
I wouldn't call it a leak, but you treat end_ inconsistently. It seems like you are treating Size and Capacity as equivalent values, they are not.
Either end_ should point one past the allocated (but not necessarily populated) memory, and you return data + size in end(), or it should point one past the last element, and you should store size_t capacity_ not size_t size_;
Here is solution without memory leak. Thank you.
#pragma once
#include <cstdlib>
using namespace std;
template <typename T>
class SimpleVector {
public:
SimpleVector() {
data_ = nullptr;
end_ = data_;
size_ = 0;
capacity_ = 0;
}
explicit SimpleVector(size_t size) {
data_ = new T[size];
end_ = data_ + size;
size_ = size;
capacity_ = size;
}
SimpleVector(const SimpleVector& that)
: data_(that.data_)
, end_(that.end_)
, size_(that.size_)
, capacity_(that.capacity_) {}
SimpleVector& operator = (const SimpleVector& that) {
data_ = that.data_;
end_ = that.end_;
size_ = that.size_;
capacity_ = that.capacity_;
}
~SimpleVector() { delete[] data_; }
T& operator[](size_t index) {
return data_[index];
}
T* begin() const { return data_; }
T* end() const { return data_ + size_; }
size_t Capacity() const { return capacity_; }
size_t Size() const { return size_; }
void PushBack(const T& value) {
if (size_ == capacity_) {
if (capacity_ == 0) { // т. е. создали конструктором по умолчанию, size_ = 0
data_ = new T[1];
capacity_ = 1;
data_[size_] = value;
++size_;
end_ = data_ + size_;
}
else if (capacity_ == size_) { // т. е. capacity_ == size_
T* local_data = new T[2 * size_];
for (size_t i = 0; i < size_; ++i) {
local_data[i] = data_[i];
}
delete[] data_;
data_ = new T[2 * size_];
for (size_t i = 0; i < size_; ++i) {
data_[i] = local_data[i];
}
delete[] local_data;
data_[size_] = value;
size_++;
capacity_ *= 2;
end_ = data_ + size_;
}
}
else {
data_[size_] = value;
size_++;
}
}
private:
T *data_;
T *end_;
size_t size_;
size_t capacity_;
};
I can't seem to overload the << operator correctly. This is the code I have so far and the instructions for my assignment will be down below. If you point out other mistakes I made that would be kind, BUT my questions is how do I correctly overload my << operator in my case?
INTCOLLECTION.h:
#ifndef INTCOLLECTION_H
#define INTCOLLECTION_H
// Allocate memory in chunks of ints of this size.
const int CHUNK_SIZE = 5;
class IntCollection
{
private:
// The number of ints currently stored in the int
int size;
// the total number of elements available for storage
// in the data array
int capacity;
// A pointer to the dynamically allocated data array
int* data;
// a private member function to allocate more memory
// if necessary
void addCapacity();
public:
// Constructor
IntCollection();
// Destructor
~IntCollection();
// Copy constructor:
IntCollection(const IntCollection &c);
void add(int value);
int get(int index);
int getSize();
IntCollection& operator=(const IntCollection &c);
bool operator==(const IntCollection &c);
IntCollection& operator<<(int value);
};
#endif
INTCOLLECTION.cpp:
#include "IntCollection.h"
#include <iostream>
#include <cstdlib>
using namespace std;
IntCollection::IntCollection()
{
// Initialize member data to reflect an empty
// IntCollection
size = capacity = 0;
data = NULL;
}
IntCollection::~IntCollection()
{
delete [] data;
}
IntCollection::IntCollection(const IntCollection &c)
{
size = c.size;
capacity = c.capacity;
data = c.data;
for(int i = 0; i < c.size; i++)
{
data[i] = c.data[i];
}
}
void IntCollection::addCapacity()
{
// Create a new, bigger buffer, copy the current data to
// it, delete the old buffer, and point our data
// pointer to the new buffer
int *newData;
data = new int[capacity];
capacity += CHUNK_SIZE;
newData = new int[capacity];
for(int i = 0; i < size; i++)
{
newData[i] = data[i];
delete [] data;
data = newData;
}
}
void IntCollection::add(int value)
{
//first, allocate more memory if we need to
if(size == capacity)
{
addCapacity();
}
//Now, add the data to our array and increment size
data[size++] = value;
}
int IntCollection::get(int index)
{
if (index < 0 || index >= size)
{
cout << "ERROR: get() trying to access index out of range.\n";
exit(1);
}
return data[index];
}
int IntCollection::getSize()
{
return size;
}
IntCollection& IntCollection::operator=(const IntCollection &c)
{
size = c.size;
capacity = c.capacity;
data = c.data;
return *this;
}
bool IntCollection::operator==(const IntCollection &c)
{
if((size == c.size) && (capacity == c.capacity))
{
for(int m = 0; m < size; m++)
{
if(data[m] == c.data[m])
{
continue;
}
else
{
return false;
}
}
}
return true;
}
IntCollection& IntCollection::operator<<(int value)
{
return value; // <-- THIS IS WHERE I GET LOST!
/* I also tried changing the parameters to
(IntCollection &b, int value) to return b
but my teacher wants only one parameter
and it wasn't working that way either. */
}
INSTRUCTIONS:
For this assignment you will add a copy constructor, a destructor, and three overloaded operators to the IntCollection class. In the design diagram below, the black member functions represent code that has already been implemented. You will be implementing the green items. Each item that you will be adding to the class is described below the diagram.
Private:
int size // the number of ints currently stored in the int collection
int capacity // total number of elements available in data array
int* data // a pointer to the dynamically allocated data array
void addCapacity(); // private function to allocate more memory if necessary
Public:
IntCollection()
~IntCollection()
IntCollection(const IntCollection &c)
void add(int value)
int get(int index)
int getSize()
IntCollection& operator=(const IntCollection &c)
bool operator==(const IntCollection &c)
IntCollection& operator<<(int value)
The Copy Constructor. The copy constructor should perform a deep copy of the argument object, i.e. it should construct an IntCollection with the same size and capacity as the argument, with its own complete copy of the argument's data array.
The Assignment Operator (=). The assignment operator should also perform a deep copy of the argument object. It must return itself (or more efficiently, a reference to itself) in order to support multiple assignments on the same line, e.g. a = b = c. If you implement your assignment operator first it could be used in the copy constructor, but this is not a requirement.
The Is Equals operator (==). The "is equals" operator should return true if the argument object has the same size as the receiving object, and the values in both objects’ data arrays are identical.
The insertion operator (<<). The insertion operator should add the int parameter into the receiving IntCollection. The functionality is exactly the same as the add() function, i.e. add ints to the collection. Note, however, that this function must return a reference to itself in order to support multiple insertions on the same line, e.g. c << 45 << -210. Unlike the assignment operator, this return must be done by reference, because each insertion actually modifies the IntCollection object, and insertion is done from left to right.
The destructor. Function add() calls addCapacity() to allocate memory when it needs more room. Nowhere in this program is the memory deallocated with delete [], which means we have a memory leak! Add a destructor which correctly handles this.
addCapacity. Note that addCapacity() is a private member function. What happens if you try to call it from outside the class, i.e. by adding the line below to main()?
You need to return *this, i.e. the object being operated upon. Returning "by reference" has the same syntax as returning "by value"; the only difference is in the addition of & to the function declaration, which is already provided.
Try this:
IntCollection& IntCollection::operator<<(int value)
{
add(value);
return *this;
}
I am getting all kinds of memory errors when testing the code in the header file. Please help me by letting me know what's wrong in the same. Thank you!
The code in question is the one in the code block below. It has comments so its pretty self explanatory.
Thank you very much for your help!!!
#include <algorithm>
class sorted_sc_array {
public:
sorted_sc_array() : size_(0), ptr_(nullptr), arr_len(1000) {
ptr_ = new signed char[arr_len];
}
~sorted_sc_array() { delete[] ptr_; }
// IMPLEMENT ME (DONE!!!! SOME EDITS REQD)
sorted_sc_array(const sorted_sc_array& A) {this->size_ = A.size_; this->ptr_ = A.ptr_; /* change this to match the definition of operator=() function */}
// IMPLEMENT ME (DONE!!!!)
sorted_sc_array& operator=(const sorted_sc_array& A) {
if (this == &A) return *this;
delete[] ptr_;
size_ = A.size_;
if (size_ == 0) ptr_ = nullptr;
else {
ptr_ = new signed char[size_];
std::copy(A.ptr_, A.ptr_ + size_, ptr_);
}
return *this;
}
// RETURNS SIZE OF THE ARRAY (i.e. HOW MANY ELEMENTS IT STORES)
int size() const { return size_; }
// RETURNS RAW POINTER TO THE ACTUAL DATA, CAN BE INVOKED AT ANY TIME
const signed char* data() const { return ptr_; }
// IMPLEMENT ME: AFTER INSERT COMPLETES THE ARRAY MUST BE IN ASCENDING ORDER (TBD!!!)
void insert(signed char c) {
if (size_ < arr_len) {
ptr_[size_++] = c;
std::sort(ptr_, ptr_ + size_);
}
else {
int arr_len_new = arr_len*2;
ptr_ = new signed char[arr_len_new];
std::copy(ptr_, ptr_ + size_, ptr_);
ptr_[size_++] = c;
std::sort(ptr_, ptr_ + size_);
}
// maybe use std::sort (myvector.begin(), myvector.end());
// if inefficient, use binary insertion
}
private:
int size_; // size of the array
signed char* ptr_; // pointer to the array
unsigned int arr_len; // dynamic mem alloc for array
}; // class sorted_sc_array
The insert() function has a number of errors when it's growing the array.
It doesn't update arr_len with the new length.
It's reassigning ptr_ with the new array before it has copied the values from the old array, and then it's copying the array to itself. You need to use a temporary variable for this.
It never frees the old array.
This fixes those problems:
void insert(signed char c) {
if (size_ < arr_len) {
ptr_[size_++] = c;
std::sort(ptr_, ptr_ + size_);
}
else {
arr_len = arr_len*2;
signed char *ptr_new = new signed char[arr_len];
std::copy(ptr_, ptr_ + size_, ptr_new);
delete[] ptr_;
ptr_ = ptr_new;
ptr_[size_++] = c;
std::sort(ptr_, ptr_ + size_);
}
You don't specify, what concrete errors you get. Although, most likely your problems come from ignoring the arr_len member. You copy size_ and allocate memory properly, but you ignore arr_len when initializing or copying. This produces problems later on, when you access and compare the uninitialized arr_len.