Pinpointing the problem
First of all, let us be clear about exactly what the problem is by illustrating the state of the memory after each statement.
int main() {
auto p = (Person*)malloc(sizeof(Person));
// +---+ +-------+
// | p | -> | ~~~~~ |
// +---+ +-------+
p = new(p)Person();
// +---+ +-------+
// | p | -> | name |
// +---+ +-------+
p->name=Test1::MSG1;
// +---+ +-------+ +---...
// | p | -> | name | -> |Something...
// +---+ +-------+ +---...
free(p);
// +---+ +---...
// | p | |Something...
// +---+ +---...
return 0;
}
As you can see, calling free(p)
freed up the memory originally allocated by malloc
, but it didn't free the memory allocated by p->name
when it was assigned to.
This is your leak.
Solving the problem
There are two aspects to having a Person
object on the heap:
- A memory allocation—handled by
malloc
/free
here.
- Initializing and Finalizing that memory—handled by calls to constructors and destructors.
You're lacking the call to the destructor, hence resources held by Person
are leaked. Here it's memory, but if Person
held a lock you could have a forever locked mutex, etc... executing the destructor is therefore necessary.
The C-style approach is to call the destructor yourself:
int main() {
auto p = (Person*)malloc(sizeof(Person));
p = new(p) Person();
p->name = Test1::MSG1;
std::cout << "name: "<< p->name << "\n";
// Problem "fixed".
p->~Person();
free(p);
std::cout << "done" << "\n";
return 0;
}
However this is not idiomatic C++: it's error prone, etc...
The C++ approach is to use RAII to ensure that when p
goes out of scope, all its resources are properly disposed of: the destructor of Person
is executed and the memory allocated for Person
itself is freed.
First of all, we're going to create some helpers. I used the c
namespace since I don't know the name of the C library you use, but I invite you to be more specific:
namespace c {
struct Disposer<T> {
void operator()(T* p) {
p->~T();
free(p);
}
};
template <typename T>
using UniquePointer<T> = std::unique_ptr<T, Disposer<T>>;
template <typename T, typename... Args>
UniquePointer<T> make_unique(T* t, Args&&... args) {
try {
new (t) T(std::forward<Args>(args)...);
} catch(...) {
free(t);
throw;
}
return UniquePointer{t};
}
} // namespace c
And with that, we can improve the original example:
int main() {
auto raw = (Person*) malloc(sizeof(Person));
auto p = c::make_unique(raw);
p->name = Test1::MSG1;
std::cout << "name: "<< p->name << "\n";
// No need to call the destructor or free ourselves, welcome to RAII.
std::cout << "done" << "\n";
return 0;
}
Note: Do not use std::endl
, use '\n'
or "\n"
instead. std::endl
calls .flush()
on top of putting an end of line, which is rarely what you want -- it slows things down.