Mechanism of Move Semantic - c++

I've started to learn C++ but I have some doubts about the move semantics. When the book says that the move constructor of A "steals" the attributes of an rvalue B, is it means that the pointer to a value V switch from B to A? So in this way, A doesn't need to allocate another portion of memory?
If so, why in this code
class CBuffer{
private:
int size;
int csize;
char* ptr;
char rtp;
public:
CBuffer(int size): size(size), csize(0){
ptr = new char[size];
*ptr = 'a';
}
CBuffer(const CBuffer& source): size(3), csize(source.csize) {
this->ptr = new char[size];
memcpy(this->ptr, source.ptr, size);
}
CBuffer(CBuffer&& source){
this->ptr = source.ptr;
this->size = source.size;
std::cout << &this->ptr << std::endl;
std::cout << &source.ptr << std::endl;
source.ptr = nullptr;
}
};
int main(){
CBuffer a = CBuffer(1);
CBuffer b = std::move(a);
CBuffer c = b;
the lines
std::cout << &this->ptr << std::endl;
std::cout << &source.ptr << std::endl;
print different addresses?

The move semantics means that instead of creating a copy of another object, we allow the object to take any dynamically allocated memory inside of this other object.
We do so by swapping any dynamically allocated memory between the two, meaning, that the value (meaning, the memory address that is stored in the pointer) of the pointer in the first object will be now the value of the pointer in the new object, and we put a nullptr in the rvalue reference instead, so when it is destroyed, it will destroy it as well.
I have added a few things to the code:
I have initialized all of the variables in the initializer list. Also, I have removed the hard-coded initialization of size in the copy-ctor.
It is common to use the name other for the other object in copy/move operations.
An Error: you must put a value inside of the ptr, it is a bad practice not initializing your variables. Actually, it makes an invalid delete if we add a dtor, and use the swap idiom here (explained below).
Last but not least, the move ctor can be called on a live object, and instead of deleting the current ptr, you put nullptr on other, which means that is never deleted. for example, if main was:
int main()
{
CBuffer a{1};
CBuffer b{2};
a = std::move(b); // legal, but causes memory leak in your code, the value of a.ptr is never deleted!
}
How does the swap idiom helps us?!
Let's look at my example; When we swap the values (meaning a.ptr = old b.ptr and b.ptr = old a.ptr), whenever b is deleted, the "old" a.ptr, which now resides in b will be deleted, since we just swapped them. Usually, when we use the move semantics, the rvalue is going to be destroyed soon, and we just "ride" on its destruction in order to prevent memory leaks.
Your code should look like that (added some prints for better understanding):
class CBuffer{
private:
int size = 0;
int csize = 0;
char* ptr = nullptr; // very important!!!
char rtp = 0;
int id = 0;
public:
static int get_current_count()
{
static int object_cnt = 1;
return object_cnt++;
}
CBuffer(int size) :
size(size),
csize(0),
ptr(new char[size]),
rtp(0)
{
id = get_current_count();
std::cout << "On ctor of " << id << std::endl;
ptr[0] = 'a';
}
CBuffer(const CBuffer& other) :
size(other.size),
csize(other.csize),
ptr(new char[size]),
id(get_current_count())
{
std::cout << "On copy ctor of " << other.id << " to " << this->id << std::endl;
std::memcpy(this->ptr, other.ptr, size);
}
CBuffer(CBuffer&& other) :
size(other.size),
csize(other.csize),
rtp(other.rtp),
id(get_current_count())
{
std::cout << "On move ctor of " << other.id << " to " << this->id << std::endl;
std::swap(this->ptr, other.ptr);
}
~CBuffer()
{
std::cout << "On dtor of " << id << std::endl;
delete[] ptr;
}
};
int main()
{
CBuffer a = CBuffer(1);
CBuffer b = std::move(a);
CBuffer c = b;
}
And the output is:
On ctor of 1
On move ctor of 1 to 2
On copy ctor of 2 to 3
On dtor of 3
On dtor of 2
On dtor of 1

Related

C++ Dynamic Member Arrays Deleted Right Before Destructor Called

I'm working on an AI project and have started to implement a NeuralNetwork class. I just want to get something basic down so I used some malloc statements with some placement news and finally delete[]s.
However, once the NeuralNetwork object (created on the stack in the main function) is about to be deleted (I set a breakpoint at the start of the destructor), my arrays seem to have been prematurely deleted (value 0xcccccccc) and the delete[] statements therefore throw access violations.
Through further investigation I found out that this deleting happens right between the last Vector object being destructed and the start of the destructor of my NeuralNetwork object being called.
I made sure to implement both copy constructors and assignment operators to make sure no copying was taking place without me noticing.
I'm really baffled with this problem and hope that someone can catch my mistake. Source code will follow:
NeuralNetwork::NeuralNetwork(const std::initializer_list<size_t>& l):
m_size(l.size() - 1),
m_weights(static_cast<Matrix<double>*>(malloc(sizeof(Matrix<double>) * m_size))),
m_biases(static_cast<Vector<double>*>(malloc(sizeof(Vector<double>) * m_size)))
{
size_t index = 0;
auto itr = l.begin();
for (auto next = itr + 1; next != l.end(); ++next, ++itr, ++index)
{
new (m_weights + index) Matrix<double>(*next, *itr);
new (m_biases + index) Vector<double>(*next);
}
}
NeuralNetwork::NeuralNetwork(const NeuralNetwork& nn) :
m_size(nn.m_size),
m_weights(static_cast<Matrix<double>*>(malloc(sizeof(Matrix<double>)* m_size))),
m_biases(static_cast<Vector<double>*>(malloc(sizeof(Vector<double>)* m_size)))
{
for (size_t index = 0; index < m_size; ++index)
{
new (m_weights + index) Matrix<double>(nn.m_weights[index]);
new (m_biases + index) Vector<double>(nn.m_biases[index]);
}
}
NeuralNetwork::NeuralNetwork(NeuralNetwork&& nn) noexcept :
m_size(nn.m_size),
m_weights(nn.m_weights),
m_biases(nn.m_biases)
{
nn.m_size = 0;
nn.m_weights = nullptr;
nn.m_biases = nullptr;
}
NeuralNetwork::~NeuralNetwork()
{
delete[] m_weights; // exception thrown here, value is 0xcccccccc, nullptr
delete[] m_biases;
}
Main code:
int main()
{
NeuralNetwork nn{ 2, 1 };
Vector<double> input(2);
input.Get(0) = 0.5;
input.Get(1) = 0.25;
Vector<double> output = nn.Forward(input); // just does the math, nothing special
for (size_t i = 0; i < output.GetSize(); ++i)
std::cout << output.Get(i) << " ";
std::cout << std::endl;
}
In case any important source code is missing please let me know, thanks!
There is a big difference between malloc/free and new/delete and new[] / delete[]
malloc will allocate an unformated chunk of memory and free will free it
new will allocate and initialize that region and delete will call the destructor
sometimes it might work to use malloc and delete but it's a bad idea
new[] will also keep a few extra info before the the returned memory to know how many objects need to be deleted
Usefull links:
https://www.geeksforgeeks.org/placement-new-operator-cpp/
You should never write such code:
You should never have to use malloc/free in a C++ program.
Allocation and desallocation should match.
Dynamic memory allocation has surely more overhead that default initialization you try to avoid.
Your code would miserably failed if initializer list is empty.
Code has memory leaks.
If you define a copy constructor, then you should also define assignment operator (same for move constructor).
Standard library already do most relavant optimization. For example,, for a std::vector the constructor of an item will be only called when you call emplace_back.
You should really write standard code. It does not worth the trouble to write bugged code for marginal performance improvement.
Your class declaration should really look something like:
class NeuralNetwork
{
public:
NeuralNetwork(const std::initializer_list<size_t>& l);
// Other constructors as appropriate here…
private:
std::vector<Matrix<double>> m_weights;
std::vector<Vector<double>> m_biases;
};
And constructor should look similar to that (code not tested):
NeuralNetwork::NeuralNetwork(const std::initializer_list<size_t>& l):
{
if (l.empty()
{
// might assert in debug or throw an exception...
return;
}
m_weights.reserve(m_size);
m_biases.reserve(m_size);
auto itr = l.begin();
for (auto next = itr + 1; next != l.end(); ++next, ++itr, ++index)
{
m_weights.emplace(*next, *itr);
m_biases.emplace(*next);
}
}
Other constructors, assignment operators and destructors should be easier to implement, more robust and performance very similar.
As you are using C++11 features already, you can also use std::vector::emplace_back(), which will deal with placement new internally.
Example:
#include <iostream>
#include <initializer_list>
#include <vector>
template<class T> class Matrix {
public:
Matrix() {std::cout << "Matrix() " << this << std::endl;}
Matrix(int width,int height):w(width),h(height) {std::cout << "Matrix(" << w << "x" << h << ") " << this << std::endl;}
~Matrix() {std::cout << "Matrix(" << w << "x" << h << ") " << this << " goodbye" << std::endl;}
private:
int w,h;
};
class NN {
public:
NN()=default;
NN(const std::initializer_list<size_t> &l);
private:
std::vector<Matrix<double>> m_weights;
};
NN::NN(const std::initializer_list<size_t> &l) {
m_weights.reserve(l.size()-1); // or deal with move constructors
auto itr = l.begin();
for (auto next = itr + 1; next != l.end(); ++next, ++itr)
{
m_weights.emplace_back(*next, *itr);
}
}
int main() {
NN test{2,3,3,2};
return 0;
}
Output (from https://ideone.com/yHGAMc):
Matrix(3x2) 0x5638f59aae70
Matrix(3x3) 0x5638f59aae78
Matrix(2x3) 0x5638f59aae80
Matrix(3x2) 0x5638f59aae70 goodbye
Matrix(3x3) 0x5638f59aae78 goodbye
Matrix(2x3) 0x5638f59aae80 goodbye
So the default constructor was not involved and objects were destructed properly.

Can anybody explain me why this code is not working?

I'm learning C++. Now, I'm trying to make one sample related with overloading operators of an object. My object (called Contador) has different methods and variables which help user to count iterations.
Header file of the object:
class Contador
{
private:
int* Valor;
int* Salto;
public:
Contador(int Valor_Inicio = 0, int Salto = 1);
~Contador();
inline int Get_Valor() const { return *Valor; }
inline int Get_Salto() const { return *Salto; }
inline void Incremento() { Set_Valor(Get_Valor() + Get_Salto()); }
inline void operator++ () { Set_Valor(Get_Valor() + Get_Salto()); }
void Set_Valor(int Valor);
void Set_Salto(int Salto);
};
Cpp file of the object:
// Librerias Propias
#include "Contador.h"
Contador::Contador(int Valor_Inicio, int Salto)
{
Set_Valor(Valor_Inicio);
Set_Salto(Salto);
}
Contador::~Contador()
{
delete Contador::Valor;
delete Contador::Salto;
}
void Contador::Set_Valor(int Valor)
{
delete Contador::Valor;
Contador::Valor = new int(Valor);
}
void Contador::Set_Salto(int Salto)
{
delete Contador::Salto;
Contador::Salto = new int(Salto);
}
The main() function of the sample has 2 different for loops. In the first one, I call Incremento() method and in the second one I call the overloaded operator.
Main function:
void main()
{
// Genero el elemento de analisis.
Contador* contador = new Contador();
// Realizo el bucle con la función de incremento.
std::cout << "Incremento()" << std::endl;
for (contador->Set_Valor(0); contador->Get_Valor() < 3; contador->Incremento())
{
// Escribo algo.
std::cout << "Iteracion actual: " << contador->Get_Valor() << std::endl;
}
// Realizo el bucle on el operador sobrecargado
std::cout << "operador sobrecargado" << std::endl;
for (contador->Set_Valor(0); contador->Get_Valor() < 3; contador++)
{
// Escribo algo.
std::cout << "Iteracion actual: " << contador->Get_Valor() << std::endl;
}
}
The problem appears when main function passes the first iteration of the second loop. It throws one exception in Get_Valor() method.
It seems to me that it change the memory addres of the pointer Valorin some place, but I can`t find where.
Can anybody help me?
Thanks.
contador++ does not do what you think it does - contador is a pointer, not a Contador, so it will make contador point to something that does not exist.
You need to dereference the pointer.
However, *contador++ also increments contador - it is *(contador++) - and (*contador)++ does not compile because you have only overloaded the prefix operator (the postfix operator has the prototype operator++(int).
So, ++*contador will do what you want.
You can avoid many similar problems, and the clunky syntax, by not using pointers unnecessarily.
The expression contador++ increments the address that contador (a pointer) points to! So, after the first iteration, the pointer will be completely invalid.
To call the increment operator, you need: ++(*contador) which first dereferences the pointer to the object pointed to, then effects that object's increment operator.
3 coding issues:
main shoudl return int.
Valor and Salto are not initialized in constructor.
Contador::Set_Valor and Contador::Set_Salto requires initialized pointers (as you delete them).
Easy fix is:
class Contador
{
private:
int* Valor = nullptr;
int* Salto = nullptr;
//...
};
Last issue is in your last loop:
for (contador->Set_Valor(0); contador->Get_Valor() < 3; contador++)
As condator is a pointer (not pointing on an array), accessing condator[1] would be UB.
You wanted ++(*condator) (operator++ () is pre-increment whereas operator++ (int) is post-increment).
Finally, avoiding usage of all those pointers would simplify code (and no bother with rule of 3 you break):
class Contador
{
private:
int Valor;
int Salto;
public:
Contador(int Valor = 0, int Salto = 1) : Valor(Valor), Salto(Salto) {}
~Contador() = default;
int Get_Valor() const { return Valor; }
int Get_Salto() const { return Salto; }
void Incremento() { Set_Valor(Get_Valor() + Get_Salto()); }
void operator++ () { Set_Valor(Get_Valor() + Get_Salto()); }
void Set_Valor(int Valor) { this->Valor = Valor;}
void Set_Salto(int Salto) { this->Salto = Salto;}
};
int main()
{
Contador contador;
std::cout << "Incremento()" << std::endl;
for (contador.Set_Valor(0); contador.Get_Valor() < 3; contador.Incremento())
{
std::cout << "Iteracion actual: " << contador.Get_Valor() << std::endl;
}
std::cout << "operador sobrecargado" << std::endl;
for (contador.Set_Valor(0); contador.Get_Valor() < 3; ++contador)
{
std::cout << "Iteracion actual: " << contador.Get_Valor() << std::endl;
}
}
In addition to previous answers.
As I could see Contador* contador = new Contador(); code also contains UB (undefined behaviour)
This call is equal to constructor with parameters Contador(0, 1)
which will do Set_Valor and Set_Salto which call delete first but at this moment content of this variables is not guaranteed to be nullptr so you might corrupt data. Also compiler if it sees UB might optimize out all other code since it's already UB and it can change behaviour anyway it wants for example throw it away completely. https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633

Destructor causes corruption of the heap.

I'm playing around with destructors and I don't understand why I get an error for this code when the main function terminates.
#include <iostream>
#include <math.h>
#include <string>
#include <cstdint>
#include <cassert>
using namespace std;
class RGBA {
uint8_t _red, _blue, _green, _alpha;
int *_arr;
int _length;
public:
RGBA(int *arr, int length, uint8_t r = 0, uint8_t b = 0, uint8_t g = 0, uint8_t a = 255):
_red (r), _blue (b), _green (g), _alpha (a) {
_arr = arr;
_length = length;
}
~RGBA() {
cout << "Destroying object" << endl;
delete[] _arr;
}
void print() {
for (int i = 0; i < _length; ++i) {
cout << _arr[i] << endl;
}
cout << static_cast<int>(_red) << " " << static_cast<int>(_blue) << " " << static_cast<int>(_green) << " " << static_cast<int>(_alpha) << endl;
}
};
int main() {
int arr[3] = {1,2,3};
RGBA rgba(arr, 3);
rgba.print();
cin.get();
return 0;
}
It outputs, but then when I press Enter, it prints 'Destroying object' with the following error "This may be due to a corruption of the heap, which indicates a bug in testcpp.exe or any of the DLLs it has loaded.".
1
2
3
0 0 0 255
I use VS2010 on Win7.
The automatic storage duration variable int arr[3] will be automatically deallocated when the enclosing function exits.
Trying to delete[] it causes undefined behavior. Only objects allocated with new[] can be deallocated with delete[].
In your case, this is effectively what is happening:
int arr[3] = {1,2,3};
delete[] arr;
Your arr in main is in automatic storage. You pass it into your object, which assumes ownership and assumes the memory was dynamically allocated with new.
You should only delete [] what you new [].
~RGBA() {
cout << "Destroying object" << endl;
delete[] _arr;
}
Here was your problem because delete didn't work on static array, It always work for dynamic array. delete only work for new
int *arr = new int[3];
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
this will work perfectly.
The array you are passing, arr is being allocated on the stack in your main function. You then pass the pointer to your RGBA instance which then deletes the array in its destructor. As it was not dynamically allocated in the first place, this is a bad thing.
Deleting the array in the destructor indicates that you mean to transfer ownership of the array to that class. To do that, you need to either pass a dynamically allocated array or allocate a new array in the constructor and copy the contents of the one passed by parameter.
If you do not need ownership, simply remove the delete call in the destructor.
That's because you aren't supposed to delete the array.
The implementation of your destructor is correct, but the array is not allocated with new[], so you must not de-allocating with delete[].
If you'd replace your array in your main with it's modernized cousin, std::array, and the pointer in your class by std::vector, you would see that assigning a array to a vector would require an allocation on the heap and a copy of each element in your array.
A quick way to fix the code would be to allocate the array using new[]:
int* arr = new int[3]{1, 2, 3};
RGBA rgba(arr, 3);
rgba.print();
Now that the array is allocated using new[], it can be safely deleted using delete[].
However, please note that in most modern code, you either use std::array<T, N>, std::vector<T> or std::unique_ptr<T[]> to manage arrays.
The resulting code that use arrays and vectors would be like this:
#include <iostream>
#include <vector>
#include <array>
struct RGBA {
std::uint8_t red = 0, green = 0, blue = 0, alpha = 255;
std::vector<int> arr;
RGBA(std::vector<int> _arr) : arr{std::move(_arr)} {}
// No destructor needed. vector manages it's own resources.
void print() {
for (auto&& number : arr) {
std::cout << number << std::endl;
}
std::cout << static_cast<int>(red) << " "
<< static_cast<int>(blue) << " "
<< static_cast<int>(green) << " "
<< static_cast<int>(alpha) << std::endl;
}
};
int main() {
// Create a constant array on the stack the modern way
constexpr std::array<int, 3> arr{1, 2, 3};
// Copy array elements in the vector
RGBA rgba{std::vector<int>{arr.begin(), arr.end()}};
rgba.print();
cin.get();
}

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

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);
}