A bunch of unclear things with the destructor in C++ - c++

I've written some very simple code in C++ to do some simple manipulations of vectors. This is the content of the file vector.h:
#ifndef VECTOR_H_INCLUDED
#define VECTOR_H_INCLUDED
class Vector {
int *coordinates;
int *size;
public:
Vector(int vector_size);
Vector(int*,int);
~Vector();
void print(void);
Vector operator +(Vector);
};
#endif
and this is the implementation (file: vector.cpp):
#include "vector.h"
#include <iostream>
using namespace std;
Vector::Vector(int vector_size) {
coordinates = new int[vector_size];
size = new int;
*size = vector_size;
}
Vector::Vector(int* vector_coordinates, int vector_size){
coordinates = vector_coordinates;
size = new int;
*size = vector_size;
}
void Vector::print(void){
cout << "[";
for (unsigned short int index =0; index<*size; index++){
cout << coordinates[index];
if (index < *size-1){cout << ", ";};
}
cout << "]\n";
}
Vector Vector::operator+ (Vector other) {
Vector temp(*(other.size));
if ((*temp.size)!=(*(this->size))){
throw 100;
}
int* temp_c = new int[*(other.size)];
int* other_c = other.coordinates;
for (unsigned short int index =0; index<*size; index++){
temp_c[index] = coordinates[index] + other_c[index];
}
temp.coordinates = temp_c;
return (temp);
}
Vector::~Vector(){
delete[] coordinates;
delete size;
}
From my main.cpp, I do the following:
#include <iostream>
using namespace std;
#include "vector/vector.h"
const int size = 3;
int main() {
int *xxx = new int[size];
xxx[0]=4; xxx[1]=5; xxx[2]=-6;
Vector v(xxx,size);// v = [4, 5, -6]
Vector w(size);// w is a vector of size 3
w = v+v; // w should be w=[8,10,-12]
w.print();
return 0;
}
The result is then:
[148836464, 5, -6, 17, 148836384, 0, 0, 17, 0, 0, 0, 17, 3, 0, 0, 17, 0, 0, 0, 17, 148836480, 0, 0, 17, 0, 10, -12, 135025, 0, 0, 0, 0, 0, 0, , 0, 0,Segmentation fault
If I remove the two lines from the destructor:
delete[] coordinates;
delete size;
everything works as expected and the program outputs:
[8, 10, -12]
I would appreciate any explanations...
Update 1: I changed my operator+ method to the following, but the problem was not resolved:
Vector Vector::operator+(Vector other) {
int size_of_other = *(other.size);
int size_of_me = *(this->size);
if (size_of_other != size_of_me) {
throw 100;
}
int* temp_c = new int[size_of_me];
int* other_c = other.coordinates;
for (unsigned short int index = 0; index < size_of_me; index++) {
temp_c[index] = coordinates[index] + other_c[index];
}
Vector temp(temp_c,size_of_me);
return (temp);
}
Update 2: I noticed that using the operator:
Vector Vector::operator+(Vector other);
I wouldn't get the desired result. The modification that made it work was:
const Vector& Vector::operator+(const Vector& other) {
Vector temp(other.size);
for (unsigned short int index = 0; index < size; index++) {
cout << "("<< index <<") "<<coordinates[index] << "+"
<<other.coordinates[index] << ", "<< endl;
temp.coordinates[index] = coordinates[index] + other.coordinates[index];
}
return (temp);
}
Update 3: After update #2, I was getting a warning from the compiler that I return the local 'temp'. I changed my code to the following which completely resolved all problems and works fine (I return a copy of temp):
const Vector Vector::operator+(const Vector& other) const{
Vector temp(other.size);
for (unsigned short int index = 0; index < size; index++) {
temp.coordinates[index] = coordinates[index] + other.coordinates[index];
}
return *(new Vector(temp));
}

Your Vector::operator+ has at least one bug:
int* temp_c = new int;
...
temp_c[index] =
You are indexing temp_c when it was allocated with only a single integer. So your loop is stomping on some other memory, causing undefined behaviour.
You will also need to define a copy constructor so that you can properly use your Vector objects. The compiler generates a default copy constructor, but the default one is generally not suitable for objects that contain pointers.
This line:
temp.coordinates = temp_c;
causes a memory leak, because it overwrites the previously allocated temp.coordinates vector.
Update 3: Your code
return *(new Vector(temp));
while it appears to work, is still a memory leak. You are allocating a new Vector, then the compiler calls the copy constructor to copy that into the return value of your function. Nobody ever deletes the Vector object you just created, so there is a memory leak.
The solution is to write a copy constructor, instead of relying on the compiler-generated default copy constructor. All the other answers to your question have said the same thing. It is required that you do this for a correct program.

Your class needs a copy constructor and copy assignment operator to work correctly. A big hint that they are needed is that the destructor is not {}. See the "Rule of Three".
To get a bit better and more modern, you could also consider a move constructor and move assignment operator.

Try the code below which:
Implements a default constructor. This garauntees that however your object is constructed, your internal variables are going to be pointing at something on the heap or at NULL so any delete [] calls aren't going to die horribly.
Implements a copy constructor. Default copy constructors don't copy memory on the heap so that was going to be a serious problem for you.
Implements an assignment operator. Again this avoids shallow copies.
Removes size as a pointer; On most systems, pointers are the same size as integers so making size a pointer just makes things unnecessarily complicated.
Fixes the addition constructor by avoiding intermediate allocations. You had a temporary local variable there so make use of it instead of allocating several extra intermediate objects.
...take a look:
// VectorImplementation.cpp : Defines the entry point for the console application.
//
#include <iostream>
using namespace std;
class Vector {
int *coordinates;
int size;
public:
Vector();
Vector(int vector_size);
Vector(int*,int);
Vector(const Vector& v);
~Vector();
Vector operator +(Vector);
Vector& operator =(const Vector & other);
void print(void);
};
Vector::Vector() {
coordinates = NULL;
size = NULL;
}
Vector::Vector(int vector_size) {
coordinates = new int[vector_size];
size = vector_size;
}
Vector::Vector(int* vector_coordinates, int vector_size){
coordinates = vector_coordinates;
size = vector_size;
}
Vector::Vector(const Vector& v) {
size = v.size;
coordinates = new int[size];
memcpy(coordinates,v.coordinates, sizeof(int)*size);
}
void Vector::print(void){
cout << "[";
for (unsigned short int index =0; index<size; index++){
cout << coordinates[index];
if (index < size-1){cout << ", ";};
}
cout << "]\n";
}
Vector Vector::operator+ (Vector other) {
Vector temp(other.size);
for (unsigned short int index =0; index<size; index++){
temp.coordinates[index] = coordinates[index] + other.coordinates[index];
}
return (temp);
}
Vector & Vector::operator= (const Vector & other)
{
if (this != &other) // protect against invalid self-assignment
{
// 1: allocate new memory and copy the elements
int * tmp_coordinates = new int[other.size];
memcpy(tmp_coordinates, other.coordinates, sizeof(int)*other.size);
// 2: deallocate old memory
delete [] coordinates;
// 3: assign the new memory to the object
coordinates = tmp_coordinates;
size = other.size;
}
// by convention, always return *this
return *this;
}
Vector::~Vector(){
printf("Destructing %p\n", this);
delete[] coordinates;
}
const int size = 3;
int _tmain(int argc, _TCHAR* argv[])
{
int *xxx = new int[size];
xxx[0]=4;
xxx[1]=5;
xxx[2]=-6;
Vector v(xxx,size);// v = [4, 5, -6]
Vector w(size);// w is a vector of size 3
w = v+v; // w should be w=[8,10,-12]
w.print();
return 0;
}

Doing that is a bad idea:
Vector::Vector(int* vector_coordinates, int vector_size){
coordinates = vector_coordinates;
size = new int;
*size = vector_size;
}
you assign coordinates pointer to data that you did not allocate, and then try to delete it in the destructor.
But the real reason that you get segfault is that you use the default copy constructor, and the temporary copy of v deletes the vector when it dies. You have to implement copy constructor and ensure deep copy or reference counting.
Try something like this:
Vector::Vector(const Vector& other){
size = new int(*other.size);
coordinates = new int[size];
memcpy(coordinates, other.coordinates, sizeof(int)*(*size));
}
Also, your operator+ would be much more efficient if you take const reference as an argument:
Vector Vector::operator+ (const Vector& other)

Consider the line
w = v+v; // w should be w=[8,10,-12]
a temporary object is constructed for the result of v+v, then assigned to w and destroyed.
Since you don't have and assignment operator a shallow copy is performed by the default implementation and you are working with deallocated memory.
The simple way to fix this issue is to implement a copy constructor/ assignment operator and destructor when you are allocating memory for members.

Related

Trying to define constructors. Code E0513 a value of type "float" cannot be assigned to an entity of type "float*"

After adding data members I get Code E0513 Line 49 a value of type "float" cannot be assigned to an entity of type "float*"
Found post on SO with the same issue so I tried using const_cast Line 44 and that didn't help either.gave me Code E0077 Line 44 no storage class or type specifier
I've tried to give as much information as possible. And the code was in three separate files. Not sure if this single file is in the correct format.
#include <iostream>
using namespace std;
// Class declaration usually in header
class Float_Array
{
public:
// ~~~~~~~~~~~~~~~~~~ Constructors
// Creat a Float_Array with zero elements.
Float_Array();
// Create a Float_array with 'size' elements.
Float_Array(int size);
// Creat3 a Float_Array from another Float_Array --
// Confirm no memory leaks!
Float_Array(const Float_Array& rhs);
// Free dynamic memory.
~Float_Array();
// ~~~~~~~~~~~~~~~~~~ Member functions
// Define how a Float_Array shall be assigned to another Float_Array-- be sure to prevent memory leaks
// Is this a user defined type.
Float_Array& operator=(const Float_Array& rhs);
// Resize the Float_Array to a new size.
void resize(int new_size);
// Return the number of the elements in the array.
int size();
// Overload bracket operator so client can index into FloatArray objects and access the elements.
float& operator [] (int i);
private:
//~~~~~~~~~~~~~~~~~~ Class Data Members
float* m_data_ptr; // Pointer to array of floats (dynamic memory)
int m_size; // The number of elements in the array.
};
float* m_data_ptr;
m_data_ptr = const_cast<float*>(m_data_ptr); // ~~~~ Line 44 E0077 Added this line caused this error
// empty class definition
Float_Array::Float_Array() // ~~~~
{
m_data_ptr = 0.0f; //~~~~ Line 49 E0513 ~~~~ a value of type "float" cannot be assigned to an entity of type "float*"
m_size = 0;
}
// Create array with size elements see array in template codelite page 136
// class definition
Float_Array::Float_Array(int size)
{
m_size = size;
}
// empty class definition
Float_Array::Float_Array(const Float_Array& rhs)
{
}
// ~~~~~~~~~~~~~~~~~~ Function definition
int* ResizeArray(int* array, int oldSize, int newSize)
{
// Create an array with the new size.
int* newArray = new int[newSize];
// New array is a greater size than old array.
if (newSize >= oldSize)
{
// Copy old elements to new array.
for (int i = 0; i < oldSize; ++i)
newArray[i] = array[i];
}
// New array is a lesser size than old array.
else // newSize < oldSize
{
// Copy as many old elements to new array as can fit.
for (int i = 0; i < newSize; ++i)
newArray[i] = array[i];
}
// Delete the old array.
delete[] array;
// Return a pointer to the new array.
return newArray;
}
void Print_Float_Array(Float_Array& fa)
{
cout << "{ ";
for (int i = 0; i < fa.size(); ++i)
cout << fa[i] << " ";
cout << "}" << endl << endl;
}

Segfault with std::vector =-operation to uninitialized space

I get segmentation faults when I use the =-operator to copy a struct that contains a std::vector to uninitialized memory.
The critical code looks like that:
template<typename T>
ComponentContainer
{
T* buffer;
size_t capacity;
size_t m_size;
public:
ComponentContainer();
~ComponentContainer();
size_t size();
void resize(size_t size);
T & operator[](size_t index);
};
template<typename T>
void ComponentContainer<T>::resize(size_t newSize)
{
if(this->m_size >= newSize)
{
this->m_size = newSize;
}
else
{
if(this->capacity < newSize)
{
const size_t newCapacity = capacity*2;
T* newBuffer = (T*)malloc(newCapacity*sizeof(T));
for(size_t i = 0; i<m_size; i++)
{
// checks if this->buffer[i] is valid intialized memory
if(pseudo_checkIfElementIsInitialized(i))
{
// when this is uncommented no segfault happens
//new (&newBuffer[i]) T();
newBuffer[i] = this->buffer[i]; // <- segfault happens here
}
}
this->capacity = newCapacity;
free(this->buffer);
this->buffer = newBuffer;
}
this->m_size = newSize;
}
}
The T-type is a struct with a std::vector of structs when I get the segfault.
I suspect that the std::vector =-operator uses somehow the left side variable newBuffer[i] and the segmentation fault happens since newBuffer[i] is not initialized.
Objects will be created only with in-placement new with the function T & operator[](size_t index). The malloc should only allocate the memory without initializing anything.
I tried to write a simple example but that hasn't worked out so well:
#include <iostream>
#include <vector>
struct Hello
{
Hello()
{
std::cout << "constructor" << std::endl;
}
~Hello()
{
std::cout << "destructor" << std::endl;
}
std::vector<double> v = std::vector<double>(1);
};
int main()
{
Hello* buffer = (Hello*)malloc(1*sizeof(Hello));
char* noise = (char*)buffer;
for(size_t i = 0; i<sizeof(Hello); i++)
{
noise[i] = 100;
}
auto tmp = Hello();
tmp.v[0] = 6.6;
//new (&buffer[0]) Hello();
buffer[0] = tmp;
std::cout << buffer[0].v[0] << std::endl;
return 0;
}
It works fine without segfault. I assume that is because the uninitialized memory was just by chance ok for the std::vector =-operation.
So
a) is that theory correct
and if yes
b) how to solve this problem without using a default constructor (T()) for every class that i use as T for my ComponentContainer
Well, yeah. You can't assign to an object that doesn't exist.
Uncomment the line that fixes it!
If you can't default construct, then copy construct:
new (&newBuffer[i]) T(this->buffer[i]);
And if you can't do that, then, well, you know the rest.
The malloc should only allocate the memory without initializing anything.
Is it possible that you've underestimated the weight of this statement? You don't just get memory then decide whether or not to initialise it with some values. You have to actually create objects before using them; this is not optional. You're programming C++, not manipulating bits and bytes on a tape :)

Even the Copy Assignment Operator can't assist

Kindly help me figure out where the issue is. I have followed the rule of three as well and made several modifications to the code.
#include <iostream>
using namespace std;
class AStack {
public:
AStack();
AStack(int);
AStack(const AStack&);
~AStack();
AStack& operator = (const AStack& s);
void push(int);
int pop();
int top();
bool isEmpty();
void flush();
private:
int capacity ;
int* a;
int index = -1; // Index of the top most element
};
AStack::AStack() {
a = new int[25];
capacity = 25;
}
AStack::AStack(int size) {
a = new int[size];
capacity = size;
}
AStack::AStack(const AStack& s) {
capacity = s.capacity;
delete[] a; // To avoid memory leak
a = new int[capacity];
for (int i = 0; i < capacity; i++) {
a[i] = s.a[i];
}
index = s.index;
}
AStack::~AStack() {
delete[] a;
}
AStack& AStack::operator = (const AStack& s) {
capacity = s.capacity;
delete[] a; // To avoid memory leak
int* a = new int[capacity];
for (int i = 0; i < capacity; i++) {
a[i] = s.a[i];
}
index = s.index;
return *this;
}
void AStack::push(int x) {
if (index == capacity - 1) {
cout << "\n\nThe stack is full. Couldn't insert " << x << "\n\n";
return;
}
a[++index] = x;
}
int AStack::pop() {
if (index == -1) {
cout << "\n\nNo elements to pop\n\n";
return -1;
}
return a[index--];
}
int AStack::top() {
if (index == -1) {
cout << "\n\nNo elements in the Stack\n\n";
return -1;
}
return a[index];
}
bool AStack::isEmpty() {
return (index == -1);
}
void AStack::flush() {
if (index == -1) {
cout << "\n\nNo elements in the Stack to flush\n\n";
return;
}
cout << "\n\nFlushing the Stack: ";
while (index != -1) {
cout << a[index--] << " ";
}
cout << endl << endl;
}
AStack& reverseStack(AStack& s1) {
AStack s2;
while (!s1.isEmpty()) {
s2.push(s1.pop());
}
s1 = s2;
return s1;
}
int main() {
AStack s1;
s1.push(1);
s1.push(2);
s1.push(3);
s1.push(4);
s1.push(5);
s1 = reverseStack(s1);
cout << "\n\nFlushing s1:\n";
s1.flush();
system("pause");
return 0;
}
I fail to understand how even after defining the appropriate copy assignment operator, the values in s1 after returning from the function are garbage values.
If your copy constructor is correct, and your destructor is correct, your assignment operator could be written in a much easier and safer fashion.
Currently, your assignment operator has two major flaws:
No check for self-assignment.
Changing the state of this before you know you can successfully allocate the
memory (your code is not exception safe).
The reason for your error is that the call to reverseStack returns a reference to the current object. This invoked the assignment operator, thus your assignment operator was assigning the current object to the current object. Thus issue 1 above gets triggered.
You delete yourself, and then you reallocate yourself, but where did you get the values from in the loop to assign? They were deleted, thus they're garbage.
For item 2 above, these lines change this before you allocate memory:
capacity = s.capacity;
delete[] a; // To avoid memory leak
What happens if the call to new[] throws an exception? You've messed up your object by not only changing the capacity value, but you've also destroyed your data in the object by calling delete[] prematurely.
The other issue (which needs to be fixed to use the copy/swap idiom later in the answer), is that your copy constructor is deallocating memory it never allocated:
AStack::AStack(const AStack& s) {
capacity = s.capacity;
delete[] a; // ?? What
Remove the line with the delete[] a, since you are more than likely calling delete[] on a pointer that's pointing to garbage.
Now, to rid you of these issues with the assignment operator, the copy/swap idiom should be used. This requires a working copy constructor and a working destructor before you can utilize this method. That's why we needed to fix your copy constructor first before proceeding.
#include <algorithm>
//...
AStack& AStack::operator = (AStack s)
{
std::swap(capacity, s.capacity);
std::swap(a, s.a);
std::swap(index, s.index);
return *this;
}
Note that we do not need to check for self assignment, as the object that is passed by value is a brand new, temporary object that we are taking the values from, and never the current object's values (again, this is the reason for your original code to fail).
Also, if new[] threw an exception, it would have been thrown on the call to the assignment operator when creating the temporary object that is passed by value. Thus we never get the chance to inadvertently mess up our object because of new[] throwing an exception.
Please read up on what the copy/swap idiom is, and why this is the easiest, safest, and robust way to write an assignment operator. This answer explains in detail what you need to know:
What is the copy-and-swap idiom?
Here is a live example of the fixed code. Note that there are other changes, such as removing the default constructor and making the Attack(int) destructor take a default parameter of 25.
Live Example: http://ideone.com/KbA20D

union of 2 sets; return simple object - C++

What is the best way to implement the following? I am trying to find the union of 2 sets. I am creating 2 objects (one called set1 and one called set2). I aim to create a 3rd object that is a UNION of the two without having to use a copy constructor. Using dynmanic memory allocation and pointers and/or references is a must. Thanks to anyone to solves this dilemma and any pointers (no pun intended) would help.
Thanks coders.
THE HEADER file
#ifndef INTEGERSET_H_
#define INTEGERSET_H_
class IntegerSet
{
private:
int * set;
int set_size;
public:
IntegerSet(int size); //default constructor
~IntegerSet(); //destructor
IntegerSet * unionOfSets(const IntegerSet & set2);
void insertElement(int k) const;
void printSet(int size) const;
};
#endif
THE MAIN file
#include <iostream>
#include "integerset.h"
using std::cout;
using std::endl;
int main()
{
IntegerSet set1(11);
//testing below
set1.insertElement(3);
set1.insertElement(4);
set1.insertElement(6);
set1.insertElement(10);
set1.printSet(11);
cout << endl;
IntegerSet set2(8);
set2.insertElement(3);
set2.insertElement(6);
set2.insertElement(7);
set2.printSet(11);
cout << endl;
IntegerSet * obj3 = new IntegerSet(11);
obj3 = set1.unionOfSets(set2);
obj3->printSet(11);
// system("pause");
return 0;
}
THE IMPLEMENTATION FILE
#include "integerset.h"
#include <iostream>
IntegerSet::IntegerSet(int size)
{
set = new int[size];
set_size = size;
for (int i = 0; i < size; i++)
set[i] = 0;
}
IntegerSet::~IntegerSet()
{
delete [] set;
}
void IntegerSet::insertElement(int k) const
{
(*this).set[k] = 1;
}
void IntegerSet::printSet(int size) const
{
int temp = 0;
for (int i = 0; i < size; i++)
{
if (set[i] == 1)
{
std::cout << i << " ";
temp++;
}
}
if (temp == 0)
std::cout << "----";
}
IntegerSet * IntegerSet::unionOfSets(const IntegerSet & set2) //make this return the union of 2 sets; THIS and the passed ARG reference; return address
{
return this;
}
Random morning rant
What you try to create is more a std::bitset than a std::set. A set is usually "a collection of well defined distinct objects" (Cantor's definition is a little bit more complex, but lets stick to this). As such, a set could contain several pairwise unrelated objects.
Now, after this has been said, have a look at std::bitset. Note that its size is fixed by the template parameter N. std::bitset::set is almost equivalent to your IntegerSet::insertElement, except that it throws std::out_of_range. This I recommend you to check your index for valid position:
void IntegerSet::insertElement(int k) const
{
if( k < 0 || k >= set_size)
throw std::out_of_range;
else
this->set[k] = 1;
}
However, std::bitset doesn't support unions, so it's time to address your meain conern.
IntegerSet::unionofSets
Have a look at those lines.
IntegerSet * obj3 = new IntegerSet(11);
obj3 = set1.unionOfSets(set2);
The first line initializes obj3 with a pointer which contains the memory for a newly created IntegerSet with an internal set of size 11. And in the next line, your throw that pointer away. So you're throwing away resources and create a memory leak.
If you were to create a new IntegerSet your solution would be quite simple:
IntegerSet IntegerSet::unionOfSets(const IntegerSet & set2) const
{
IntegerSet tmp (std::max(set2.set_size, this->set_size));
for(int i = 0; i < set_size; ++i)
tmp.set[i] = this->set[i];
for(int i = 0; i < set2.set_size; ++i)
tmp.set[i] |= set2.set[i];
return tmp;
}
But your implementation changes the object it has been called from, so it isn't const and a little bit different:
IntegerSet * IntegerSet::unionOfSets(const IntegerSet & set2) // not const!
{
if(set2.set_size > set_size){
// the resulting set is bigger, we need new memory
int * newset = new int[set2.set_size];
// copy old values
for(int i = 0; i < this->set_size; ++i)
newset[i] = this->set[i];
// replace old size
this->set_size = set2.set_size;
delete[] this->set; // remove old data
this->set = newset; // replace pointer
}
for(int i = 0; i < set2.set_size; ++i)
this->set[i] |= set2.set[i];
return this;
}
This should be sufficient. Keep in mind that you must not use new IntegerSet in order to create a union:
IntegerSet * obj3 = new IntegerSet(11); // new memory, lets say obj3 = 0x500a
obj3 = set1.unionOfSets(set2); // new memory gone forever
if(obj3 == &set1)
std::cout << "obj3 is a pointer to set1, changes to obj3 will affect set1" << std::endl;
If you don't want to create this behaviour use the first version with the temporary.
Also, please check whether std::set<int> is sufficient for you, as you can use std::set_union from <algorithm>.
EDIT
Provide a unionOfSets member function that creates a third IntegerSet that is the union of two existing IntegerSet instances (so the third set created by this function contains all the members in the two sets used to create it – so if one or both of the sets the union is performed on has an element in it, the third set will have that element)
In this case forget about IntegerSet * IntegerSet::unionOfSets(const IntegerSet&) and use IntegerSet IntegerSet::unionOfSets(const IntegerSet&) const (the first variant with a returned object instead of a returned pointer).
EDIT2
As you didn't follow the rule of three, the memory in the returned IntegerSet will be invalid. You would either have to implement a copy constructor/assignment operator in order to fix this, or provide a new object with dynamic storage duration (new). For this you would just have to adjust the method a little bit:
IntegerSet * IntegerSet::unionOfSets(const IntegerSet & set2) const
{
IntegerSet * tmp = new IntegerSet( set2.set_size > this->set_size ? set2.set_size : this->set_size);
for(int i = 0; i < set_size; ++i)
tmp->set[i] = this->set[i];
for(int i = 0; i < set2.set_size; ++i)
tmp->set[i] |= set2.set[i];
return tmp;
}
Using standard facilities...
std::vector is better than a hand-rolled array
std::sort and std::unique are goodness
Therefore:
std::vector<int> set1;
set1.push_back(1); // ... and others
std::sort(set1.begin(), set1.end()); // sorts
std::unique(set1.begin(), set1.end()); // removes duplicates
// same with set2
std::vector<int> set3(set1);
set3.insert(set3.end(), set2.begin(), set2.end());
std::sort(set1.begin(), set1.end()); // sorts
std::unique(set1.begin(), set1.end()); // removes duplicates

What is wrong with my park_car function?

I'm again doing a task for school and I'm implementing it slowly, I don't know why my park_car function is not working, I just wanted to make a test and the program crashes ... here is my code.
PS: I can't change the ***p2parkboxes because it is given in the starter file like most other variables. I just want to see the first element of Floor 0 as : HH-AB 1234. Your help is most appreciated.
PS2: I can't use the std::string as well it isn't allowed for the task.
#include <iostream>
#include <cstring>
using namespace std;
#define EMPTY "----------"
class Parkbox{
char *license_plate; // car's license plate
public:
Parkbox(char *s = EMPTY); // CTOR
~Parkbox(); // DTOR
char *get_plate(){return license_plate;}
};
class ParkingGarage{
Parkbox ***p2parkboxes;
//int dimensions_of_parkhouse[3]; // better with rows,columns,floors
int rows,columns,floors; // dimensions of park house
int total_num_of_cars_currently_parked;
int next_free_parking_position[3];
// PRIVATE MEMBER FUNCTION
void find_next_free_parking_position();
public:
ParkingGarage(int row, int col, int flr);// CTOR,[rows][columns][floors]
~ParkingGarage(); // DTOR
bool park_car(char*); // park car with license plate
bool fetch_car(char*); // fetch car with license plate
void show(); // show content of garage floor
// by floor
};
Parkbox::Parkbox(char *s ) { // CTOR
license_plate = new char[strlen(s)+1];
strcpy(license_plate, s);
//cout << "ParkBox CTOR" << endl;
}
Parkbox::~Parkbox() { // DTOR
delete [] license_plate;
//cout << "ParkBox DTOR" << endl;
}
ParkingGarage::ParkingGarage(int row, int col, int flr){
rows = row; columns = col; floors = flr;
p2parkboxes = new Parkbox**[row];
for (int i = 0; i < row; ++i) {
p2parkboxes[i] = new Parkbox*[col];
for (int j = 0; j < col; ++j)
p2parkboxes[i][j] = new Parkbox[flr];
}
}
ParkingGarage::~ParkingGarage(){
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < columns; ++j)
delete [] p2parkboxes[i][j];
delete [] p2parkboxes[i];
}
delete [] p2parkboxes;
}
void ParkingGarage::show(){
int i,j,k;
for (i = 0 ; i < floors; i++){
cout << "Floor" << i << endl;
for (j=0;j<rows;j++){
for (k=0;k<columns;k++){
cout << p2parkboxes[j][k][i].get_plate() << " ";
}
cout << endl;
}
}
}
bool ParkingGarage::park_car(char*s){
p2parkboxes[0][0][0] = Parkbox(s); //test
//p2parkboxes[0][0][0] = s; //test
return true;
}
int main(void) {
// a parking garage with 2 rows, 3 columns and 4 floors
ParkingGarage pg1(2, 3, 4);
pg1.park_car("HH-AB 1234");
/*pg1.park_car("HH-CD 5678");
pg1.park_car("HH-EF 1010");
pg1.park_car("HH-GH 1235");
pg1.park_car("HH-IJ 5676");
pg1.park_car("HH-LM 1017");
pg1.park_car("HH-MN 1111"); */
pg1.show();
/*pg1.fetch_car("HH-CD 5678");
pg1.show();
pg1.fetch_car("HH-IJ 5676");
pg1.show();
pg1.park_car("HH-SK 1087");
pg1.show();
pg1.park_car("SE-AB 1000");
pg1.show();
pg1.park_car("PI-XY 9999");
pg1.show(); */
return 0;
}
You did not declare the copy constructor for the Parkbox class. So, the line
p2parboxes[0][0][0] = Parkbox(s)
creates something (instance of Parkbox with a char* pointer) on the stack (and deletes it almost immediately). To correct this you might define the
Parkbox& operator = Parkbox(const Parkbox& other)
{
license_plate = new char[strlen(other.get_plate())+1];
strcpy(license_plate, other.get_plate());
return *this;
}
Let's see the workflow for the
p2parboxes[0][0][0] = Parkbox(s)
line.
First, the constructor is called and an instance of Parkbox is created on stack (we will call this tmp_Parkbox).
Inside this constructor the license_plate is allocated and let's say it points to 0xDEADBEEF location.
The copying happens (this is obvious because this is the thing that is written in code) and the p2parboxes[0][0][0] now contains the exact copy of tmp_Parkbox.
The scope for tmp_Parkbox now ends and the destructor for tmp_Parkbox is called, where the tmp_Parkbox.license_plate (0xDEADBEEF ptr) is deallocated.
p2parboxes[0][0][0] still contains a "valid" instance of Parkbox and the p2parboxes[0][0][0].license_plate is still 0xDEADBEEF which leads to the undefined behaviour, if any allocation occurs before you call the
cout << p2parboxes[0][0][0].license_plate;
Bottom line: there is nothing wrong with the line itself, the problem is hidden within the implementation details of the '=' operator.
At this point it is really better for you to use the std::string for strings and not the razor-sharp, tricky and explicit C-style direct memory management mixed with the implicit C++ copy/construction semantics. The code would also be better if you use the std::vector for dynamic arrays.
The problem here is that you do not have deep copy assignment semantics. When you assign a temporary Parkbox to the Parkbox in the parking garage, the compiler generated assignment operator makes a shallow copy of the pointer license_plate, leaving both Parkboxes pointing at the same memory location. Then the temporary Parkbox goes out of scope and deletes license_plate. Since the other Parkbox is pointing at the same spot its license_plate gets deleted, too.
There are a couple solutions. One way to solve the problem is to define an assignment operator and a copy constructor that provide proper semantics, i.e. that perform deep copies of the license plate string. The better option, and the one that makes better use of C++, is to use std::strings instead of manually allocated C-strings. I strongly suggest the second approach, though working through the first might be instructive.
From the OP:
I solved the Problem with :
void Parkbox::change_plate(char *s){
delete [] license_plate;
license_plate = new char[strlen(s)+1];
strcpy(license_plate, s);
}