EDIT: This is not a question about dynamic memory handling, it is a question about the compiler's behavior when function calls are separated by commas. This is an example of a book to demonstrate this behavior. Destructors are called when the end of the line is reached, at the ; , not when variables go out of scope. This can be checked by couting the destructor of the book class. That is, during show(l1), show(l2), show(l3) no destructor is called, neither from the local variables of the functions nor from those of the main, but all are called once it has finished the execution of the three functions.
The question is, why does the compiler not call the destructors during execution of display(l1), display(l2), display(l3), when local variables go out of scope (expected behavior), and instead call all destructors when execution reaches the ;, i.e. when display(l3) is executed, no destructor has been called yet, so by using a comma, the information is still there when l3 wants to access it.
In the following example,
#include <iostream>
#include <cstring>
using namespace std;
class Libro {
char* titulo_; int paginas_;
public:
Libro() : titulo_(new char[1]), paginas_(0) {*titulo_= 0;}
Libro(const char* t, int p) : paginas_(p) {
titulo_ = new char[strlen(t) + 1];
strcpy(titulo_, t);
}
~Libro() { delete[] titulo_; }
void paginas(int p) { paginas_ = p; }
int paginas() const { return paginas_; }
char* titulo() const { return titulo_; }
};
void mostrar(Libro l) {
cout << l.titulo() << " tiene " << l.paginas() << " paginas" << endl;
}
int main() {
Libro l1("Fundamentos de C++", 474), l2("Por Fin: C ISO", 224), l3;
l3 = l1;
mostrar(l1), mostrar(l2), mostrar(l3);
}
Despite the fact that the copy constructor is not implemented and the constructor generated by the compiler does not work correctly (it only copies the address to the memory block of title_ ), when executed it correctly displays the information on the screen, with the function show. However, when using ; instead of , , the memory access in the show(l3) call is incorrect, and the information is not displayed properly, since, as expected, the copy was not performed correctly. Do you know what the difference is between using ; and , ?
Yes. I will start with the only similarity they have, both are included in the standard in section 2.12 (my translation):
Now the differences
;
It is a punctuation symbol . Indicates up to what point an element arrives that the compiler must interpret.
,
It is a binary operator . Its behavior is to evaluate the first operand and discard its result and then evaluate the second operand and return its value. For example, the following code stores
5
in the variablei
:The comma operator (
,
) evaluates1
and discards it, then evaluates2
and discards it... and so on until it evaluates5
and returns it, the following code stores6
inj
:The comma operator evaluates
++i
(which is5
and becomes6
) and discards it, then evaluatesi++
(which will leavei
with value7
but returns6
).These examples are not useful, just illustrative, but the comma operator can be used in other more useful contexts, such as in the loop
for
:In the code above, the same loop increments and decrements the control variables without needing to litter the loop body.
Your case.
You are using the comma operator (
,
) in this statement:On the other hand, the function
mostrar
receives one book per copy:So what happens is:
mostrar
with the parameterl1
.l1
It is copied into the parameterl
.l
.l
, which being a copy ofl1
, renders its pointer useless.mostrar(l1)
(which does not discard anything because the function returns nothing).mostrar
with the parameterl2
.l2
It is copied into the parameterl
.l
.l
, which being a copy ofl2
, renders its pointer useless.mostrar(l2)
(which does not discard anything because the function returns nothing).mostrar
with the parameterl3
.l1
It is copied into the parameterl
.l
, but it is a copy ofl1
which currently contains a useless pointer as it was deleted in step 1.l
, which being a copy ofl3
which in turn is a copy ofl1
tries to erase memory that has already been erased and the program causes memory corruption.So you should receive by reference (constant) the
Libro
in the functionmostrar
:Also, as we have seen, the comma operator has nothing to do with your problem. What is happening to you is that you free a pointer twice causing a memory corruption. You can fix this by manually implementing the copy operator:
Other things to keep in mind.
The rule of five.
When a class manages a resource (in this case: memory), you have to be very careful when managing it, this is known as " The rule of five " 1 which roughly says that if you implement one of the five operations (constructor, move constructor, destructor, copy operator, move operator) you may very well have to implement the rest as well.
You mishandle resources from the start.
Your default constructor asks for memory but doesn't release it on re-allocation:
If between the default constructor and the destructor there has been an object re-allocation, the memory requested in the constructor is not freed, which is why (among other things) you need to implement the copy operator (as I have suggested).
You don't need pointers.
C++ has classes to manage strings that free the programmer from the responsibility of manually managing memory, I suggest you change your pointers to characters by
std::string
making the class much simpler:As you can see, since it
std::string
handles memory on its own, you don't need a destructor (the destructorstd::string
takes care of its memory) or handle memory manually. On the other hand, since since C++11 members can be initialized at the same point they are created, you can skip the default constructor body and let the compiler generate it for you (Libro() = default;
)1 Which before C++11 was the rule of three.