c++new-operatoroperator-keyworddelete-operatorredefinition

Redefining new and delete operators


I want to redefine new and delete operators to use a custom allocator in a c++ project with multiple translation units. Here are the redefines written in the memops.hpp file:

#pragma once
#include "../lib/mem.h"

void* operator new(size_t size) {
    return __mem_alloc(size);
}

void operator delete(void *ptr) {
    __mem_free(ptr);
}

void* operator new[](size_t size) {
    return __mem_alloc(size);
}

void operator delete[](void *ptr) {
    __mem_free(ptr);
}

My question is where to include this header file, does it make a difference? Is the alternative definition applied every where in the project?

I found this article https://www.ibm.com/docs/en/i/7.4?topic=heap-overloading-new-delete-operator, but the last paragraph confuses me.


Solution

  • Where you include memops.hpp with custom allocaters matter, depending on how you are redefining the global new and delete operators.

    In C++, when you define global operator new/operator delete in a translation unit, those become the global operators for the whole program - if and only if those are the ones linked into the final executable.

    However, you should keep in mind that if you put this definition in a header and include it in multiple .cpp files, you’ll get multiple definitions — violating the One Definition Rule (ODR).

    In addition, I would suggest you only declare methods in header file and write implementation in the single .cpp file.

    Hence, memops.hpp will look like this:

    #pragma once
    #include <cstddef>
    void* operator new(size_t size);
    void operator delete(void* ptr) noexcept;
    void* operator new[](size_t size);
    void operator delete[](void* ptr) noexcept;
    

    And memops.cpp:

    #include "memops.hpp"
    #include "../lib/mem.h"
    
    void* operator new(size_t size) {
        return __mem_alloc(size);
    }
    
    void operator delete(void* ptr) noexcept {
        __mem_free(ptr);
    }
    
    void* operator new[](size_t size) {
        return __mem_alloc(size);
    }
    
    void operator delete[](void* ptr) noexcept {
        __mem_free(ptr);
    }
    

    This way, global overloads are defined exactly once, so linker will apply them to all uses of new and delete across your program - as long as no other translation unit provides different definition.

    About the last paragraph in the article, I think, this applies mostly in the context of shared libraries. where global symbols may not propagate to other modules unless explicitly linked or included. In standard C++ projects, especially with static linking, defining the overloads in one translation unit is enough.