I see in C++ there are multiple ways to allocate and free data and I understand that when you call malloc
you should call free
and when you use the
The short answer is: don't use malloc
for C++ without a really good reason for doing so. malloc
has a number of deficiencies when used with C++, which new
was defined to overcome.
malloc
is not typesafe in any meaningful way. In C++ you are required to cast the return from void*
. This potentially introduces a lot of problems:
#include <stdlib.h>
struct foo {
double d[5];
};
int main() {
foo *f1 = malloc(1); // error, no cast
foo *f2 = static_cast<foo*>(malloc(sizeof(foo)));
foo *f3 = static_cast<foo*>(malloc(1)); // No error, bad
}
It's worse than that though. If the type in question is POD (plain old data) then you can semi-sensibly use malloc
to allocate memory for it, as f2
does in the first example.
It's not so obvious though if a type is POD. The fact that it's possible for a given type to change from POD to non-POD with no resulting compiler error and potentially very hard to debug problems is a significant factor. For example if someone (possibly another programmer, during maintenance, much later on were to make a change that caused foo
to no longer be POD then no obvious error would appear at compile time as you'd hope, e.g.:
struct foo {
double d[5];
virtual ~foo() { }
};
would make the malloc
of f2
also become bad, without any obvious diagnostics. The example here is trivial, but it's possible to accidentally introduce non-PODness much further away (e.g. in a base class, by adding a non-POD member). If you have C++11/boost you can use is_pod
to check that this assumption is correct and produce an error if it's not:
#include <type_traits>
#include <stdlib.h>
foo *safe_foo_malloc() {
static_assert(std::is_pod<foo>::value, "foo must be POD");
return static_cast<foo*>(malloc(sizeof(foo)));
}
Although boost is unable to determine if a type is POD without C++11 or some other compiler extensions.
malloc
returns NULL
if allocation fails. new
will throw std::bad_alloc
. The behaviour of later using a NULL
pointer is undefined. An exception has clean semantics when it is thrown and it is thrown from the source of the error. Wrapping malloc
with an appropriate test at every call seems tedious and error prone. (You only have to forget once to undo all that good work). An exception can be allowed to propagate to a level where a caller is able to sensibly process it, where as NULL
is much harder to pass back meaningfully. We could extend our safe_foo_malloc
function to throw an exception or exit the program or call some handler:
#include <type_traits>
#include <stdlib.h>
void my_malloc_failed_handler();
foo *safe_foo_malloc() {
static_assert(std::is_pod<foo>::value, "foo must be POD");
foo *mem = static_cast<foo*>(malloc(sizeof(foo)));
if (!mem) {
my_malloc_failed_handler();
// or throw ...
}
return mem;
}
Fundamentally malloc
is a C feature and new
is a C++ feature. As a result malloc
does not play nicely with constructors, it only looks at allocating a chunk of bytes. We could extend our safe_foo_malloc
further to use placement new
:
#include <stdlib.h>
#include <new>
void my_malloc_failed_handler();
foo *safe_foo_malloc() {
void *mem = malloc(sizeof(foo));
if (!mem) {
my_malloc_failed_handler();
// or throw ...
}
return new (mem)foo();
}
Our safe_foo_malloc
function isn't very generic - ideally we'd want something that can handle any type, not just foo
. We can achieve this with templates and variadic templates for non-default constructors:
#include <functional>
#include <new>
#include <stdlib.h>
void my_malloc_failed_handler();
template <typename T>
struct alloc {
template <typename ...Args>
static T *safe_malloc(Args&&... args) {
void *mem = malloc(sizeof(T));
if (!mem) {
my_malloc_failed_handler();
// or throw ...
}
return new (mem)T(std::forward(args)...);
}
};
Now though in fixing all the issues we identified so far we've practically reinvented the default new
operator. If you're going to use malloc
and placement new
then you might as well just use new
to begin with!