2

I am trying to instrument new with some additional information in order to track down a memory leak. I know, I can override a new operator globally, but I was surprise to discover that I cannot retrieve any information regarding the type of the object being allocated (Correct me if I am wrong). Clearly, it would be beneficial to have the type information when you decide to override the new operator.

For example, I've implemented a simple and generic version of new and delete using variadic template.

std::string to_readable_name(const char * str)
{
    int status;
    char *demangled_name = abi::__cxa_demangle(str, NULL, NULL, &status);
    if(status == 0) {
        std::string name(demangled_name);
        std::free(demangled_name);
        return name;
    }
    return "Unknown";
}

template <typename T, typename... Args>
T * generic_new(Args&&... args) throw (std::bad_alloc)
{
    const std::size_t size = sizeof(T);
    std::cout << "Allocating " << size << " bytes for " << to_readable_name(typeid(T).name()) << std::endl;
    return new T(std::forward<Args>(args)...);
};

template<typename T>
void generic_delete(T* ptr)
{
    const std::size_t size = sizeof(T);
    std::cout << "Deleting " << size << " bytes for " << to_readable_name(typeid(T).name()) << std::endl;
    delete ptr;
}

int main()
{
    auto i = generic_new<int>(0);
    std::cout << *i << std::endl;
    generic_delete(i);

    return 0;
}

My question is why hasn't new be implemented with template? This would allow developer to have information about the type of the object being allocated.

Thank you

max66
  • 65,235
  • 10
  • 71
  • 111
Giuseppe Pes
  • 7,772
  • 3
  • 52
  • 90

2 Answers2

5

Most questions about why C++ is designed how it is come down to two possibilities. Either The Design and Evolution of C++ gives a reason, or we can only speculate. I believe this falls into the latter category.

First of all, use of (and the ability to replace) operator new (both global and per-class) predates the addition of templates to the language. As such, it couldn't have been done with templates originally. Some parts of the library were switched to templates that hadn't been previously, but mostly where the benefits from doing so were extremely obvious.

Second, using a template with early template-capable compilers probably would have led to problems, especially for larger programs. The problem is that a function template isn't a function--it's a recipe for creating a function, so to speak. In other words, if it was implemented as a template, every allocator for every type would result in a separate function being instantiated. Current compilers (and linkers) are fairly good at merging those afterwards, but early ones mostly weren't. In a large system you could easily have had dozens or even hundreds of separate allocation functions created.

Finally, the template would typically still only be kind of an intermediate level between normal user code and something in the library that provides something at least roughly congruent to the current operator new and operator delete that talk to the OS (or hardware, or whatever) to figure out what pool to draw memory from, and (typically) sub-allocate pieces to the user code. Somewhere you need an (often fairly substantial) piece of code to create and manage a pool of memory. If this were handled directly in the template, it would (in practice) have to be implemented in a header, which would also lead to compile times that would probably have been fairly unacceptable on a 66 MHz Pentium or even that unbelievably fast 300 MHz DEC Alpha machine most of us could only dream about.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • I'm thinking now about my PC with 40 MB harddisk. I used Stacker (later acquired by Microsoft, and it's probably the code now behind the compress option in Windows) to sort of double that capacity. It was not a good idea: very sloooow, and that Finnish software was not entirely reliable… – Cheers and hth. - Alf Sep 14 '16 at 20:42
4

You can not override the global operator new. What you can do is provide a replacement - which happens during linking and not during compiling.

My question is why hasn't new be implemented with template?

Since new is basically a single undefined reference you can easily provide a single definition to replace it. (See Detail #1 below)

Now what would be if new were a templated function? For every template instance there would be a different undefined symbol that you'd need to provide a replacement for. You could make your definition a template, too, but who would instantiate it? You'd probably need to change name resolution rules for that (and basically the way templates work).

Or you'd instead allow operator new to be override-able. Then the compiler has to choose the correct operator new during compile time. But this has the drawback that only code that has your "new new" in its translation unit can see and use it. Now consider what happens here:

#include <memory>
// code to override new and delete
void bar(void) {
  std::unique_ptr<int> x = new int{};
}

The above call to new uses your code, but what about the call to delete buried deep inside the standard library? That call would need to see your code, too. This - in turn - would mean that the standard library code must be in your translation unit, or put differently: It needs to be implemented in a header file.

Moreover, you'd need to make sure that a - say - char * that you allocated using your new doesn't get deleted by the "standard" delete. A similar issue exists already with the class specific operators:

class Baz; // forward declaration
void freeThatBaz(Baz * b) { delete b; }

Whether this actually calls the class specific operator delete or the global one is implementation defined (since C++17, it was undefined behavior before IIRC).

So I'd say: Basically it's just not worth the effort to have such huge and complex rules in the standard for something that's not even considered "good practice".

Detail #1

Compiling the following code:

void foo(void) {
  auto x = new int{};
  auto y = new bool{};
  delete x;
  delete y;
}

leads to

[nix-shell:/tmp/wtf]$ nm -C new.o 
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 T foo()
                 U operator delete(void*, unsigned long)
                 U operator new(unsigned long)

a single undefined reference to operator new, independent of the type. If it were a template, then the template type parameter would be part of the symbol, thus you'd have 2 undefined symbols.

Community
  • 1
  • 1
Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
  • 1
    Is your point one of terminology, that one should say "replace" instead of "override"? If so then that's silly. Sorry, but it is. – Cheers and hth. - Alf Sep 14 '16 at 18:44
  • @Cheersandhth.-Alf I think correct terminology is important, but that's not the actual point here: "override" happens during compile time, "replace" during link time. During linking no new function (templates) are instantiated, thus how would you satisfy a reference to an `operator new(unsigned long)` then? You'd need to put it into the current translation unit and have name resolution do it (thus override it). But if you want to "override" all uses of `new`, then all code that calls `new` would need to see your override, including code in the STL, etc. – Daniel Jour Sep 14 '16 at 19:16
  • @Cheersandhth.-Alf I added an example of the issue I was thinking about. Does this make it more clear? – Daniel Jour Sep 14 '16 at 19:30
  • @DanielJour Regarding the example with `unique_ptr`. I am allowed to override the operators new and delete for a user define class. Wouldn't the same problem arise for overridden operators for a user define type? – Giuseppe Pes Sep 14 '16 at 20:14
  • I think the delete buried inside the lib will call my operators the linker will take care of that. If this wasn't the case, `new` and `delete` overrides for custom object wouldn't work. Example of what I mean : http:://cpp.sh/2h2t – Giuseppe Pes Sep 14 '16 at 20:25
  • @GiuseppePes That's a good point. Regarding how this is handled for `std::unique_ptr`: Exactly as I tried to suggest, the code that `delete`s must be part of *every* translation unit that uses `std::unique_ptr`. Therefore, it's [implemented in a header file](https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.4/a01404.html). – Daniel Jour Sep 14 '16 at 20:30
  • If you'd make `new` and `delete` override-able you'd need to provide your overrides (for example in a header file) to all code (translation units) that is using these operators, and you'd need to make sure that a pointer from an override `new` doesn't get to a different `delete`. In the case of user defined classes the compiler adds a safeguard here: You cannot `delete` a pointer to `Thing` when you don't have a complete definition (not forward declaration) of `Thing` (and thus see its operator delete). – Daniel Jour Sep 14 '16 at 20:36