HiveBrain v1.2.0
Get Started
← Back to all entries
snippetcppMinor

Implementing `create` and `destroy` functions to replace `new` and `delete` operators

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
newcreatedeletereplaceandfunctionsimplementingoperatorsdestroy

Problem

The environment I'm working in requires that all allocation is done through a special set of malloc/calloc functions and that a user ID is provided when allocating. The environment has no implementation of exceptions, so errors are signaled by returning NULL.

For this reason, I have tried to write a couple of C++ functions which make it easier to create and destroy C++ objects:

namespace details {
    template
    typename enable_if::value, T>::type *
    create(uint32_t module_id) {
        // use module_id
        return (T *)calloc(1, sizeof(T));
    }

    template
    typename enable_if::value, T>::type *
    create(uint32_t module_id, Args&&... args) {
        // use module_id
        T *p = (T *)calloc(1, sizeof(T));
        if (p)
            new (p) T(forward(args)...);
        return p;
    }

    template
    void destroy(typename enable_if::value, T>::type *t) {
        if (t)
            free(t);
    }

    template
    void destroy(typename enable_if::value, T>::type *t) {
        if (!t)
            return;

        t->~T();
        free(t);
    }
};

template
T *create(uint32_t module_id, Args&&... args) {
    return details::create(module_id, forward(args)...);
}

template
void destroy(T *t) {
    details::destroy(t);
}


Can this be done smarter? And are there any cases where my create/destroy functions are not handling as one would expect?

Solution

After some research, I'm convinced Christopher Creutzig is right. If you want all your allocations to go through customized functions, you're much better off replacing the global operator new. This will catch all calls to new for the translation unit, whether you replace them with create or not. That means when you create a std::vector myvec(3), it will use your operator new for any heap allocations it needs to do, instead of sidestepping your global policy.

If you need to pass a module id, operator new can handle that as well by accepting extra parameters. I'm unclear how that mixes with examples such as the std::vector heap allocations; it may require using a custom allocator for each STL container. Setting that aside, here's an example like yours following the lead on cppreference.com:

void* operator new(std::size_t cb, uint32_t module_id)
{
    // use module_id
    return calloc(1, cb);
}
void operator delete(void* ptr)
{
    free(ptr);   // I'm unsure if this requires a null check
}

type *p = new(module_id) type;


As an aside, if you're particularly tied to calling functions, I'd be tempted to remove the SFINAE specialization via is_pod and just make it an inline check via is_trivially_constructible and is_trivially_destructible...that is if checking is necessary at all.

template
T* create(uint32_t module_id)
{
    // use module_id
    T* p = calloc(1, sizeof(T));
    if (!is_trivially_constructable::value && p)
    {
        new (p) T(forward(args)...);
    }
    return p;
}

Code Snippets

void* operator new(std::size_t cb, uint32_t module_id)
{
    // use module_id
    return calloc(1, cb);
}
void operator delete(void* ptr)
{
    free(ptr);   // I'm unsure if this requires a null check
}

type *p = new(module_id) type;
template<typename T>
T* create(uint32_t module_id)
{
    // use module_id
    T* p = calloc(1, sizeof(T));
    if (!is_trivially_constructable<T>::value && p)
    {
        new (p) T(forward<Args>(args)...);
    }
    return p;
}

Context

StackExchange Code Review Q#33858, answer score: 2

Revisions (0)

No revisions yet.