Giuseppe Pes
Giuseppe Pes

Reputation: 7912

Why isn't new implemented with template?

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

Upvotes: 2

Views: 364

Answers (2)

Daniel Jour
Daniel Jour

Reputation: 16156

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.

Upvotes: 4

Jerry Coffin
Jerry Coffin

Reputation: 490158

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.

Upvotes: 5

Related Questions