Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

If I write operators new and delete for a class, do I have to write all of their overloads?

The C++ Reference page lists 8 class specific overloads for global new operators. Four of those were added for 2017 version of C++.

Class-specific allocation functions

void* T::operator new  ( std::size_t count );   
void* T::operator new[]( std::size_t count );
void* T::operator new  ( std::size_t count, std::align_val_t al ); // (since C++17)
void* T::operator new[]( std::size_t count, std::align_val_t al ); // (since C++17)

Class-specific placement allocation functions

void* T::operator new  ( std::size_t count, user-defined-args... );
void* T::operator new[]( std::size_t count, user-defined-args... );
void* T::operator new  ( std::size_t count,
    std::align_val_t al, user-defined-args... ); // (since C++17)
void* T::operator new[]( std::size_t count,
     std::align_val_t al, user-defined-args... ); // (since C++17)

That website also lists 10 class specific versions of global delete operators, of which 4 were introduced for 2017.

Class-specific usual deallocation functions

void T::operator delete  ( void* ptr );
void T::operator delete[]( void* ptr );
void T::operator delete  ( void* ptr, std::align_val_t al ); // (since C++17)
void T::operator delete[]( void* ptr, std::align_val_t al ); // (since C++17)
void T::operator delete  ( void* ptr, std::size_t sz );
void T::operator delete[]( void* ptr, std::size_t sz );
void T::operator delete  ( void* ptr, std::size_t sz, std::align_val_t al ); // (since C++17)
void T::operator delete[]( void* ptr, std::size_t sz, std::align_val_t al ); // (since C++17)

Class-specific placement deallocation functions

void T::operator delete  ( void* ptr, args... );
void T::operator delete[]( void* ptr, args... );

If I write a C++ class with new and delete operators, do I need to overload all of those? I am ignoring the replaceable global operators since I am only writing class specific operators.

This other question provides info on writing ISO compliant new and delete operators, but does not say if I should overload all of them, or just some.

The answer to this question about class specific new and delete operators does not say whether to replace all or just some of them.

If you can provide citations from the C++ Standard or comments by C++ memory experts, that would help.

like image 486
LincolnMan Avatar asked Aug 06 '17 20:08

LincolnMan


People also ask

Can new and delete operators be overloaded?

New and Delete operators can be overloaded globally or they can be overloaded for specific classes. If these operators are overloaded using member function for a class, it means that these operators are overloaded only for that specific class.

What happens when we use new and delete operator?

Memory that is dynamically allocated using the new operator can be freed using the delete operator. The delete operator calls the operator delete function, which frees memory back to the available pool. Using the delete operator also causes the class destructor (if one exists) to be called.

What actually happens when you use Delete on something allocated with new []?

The code calls operator new[] to allocate memory for 10 string object, then call the default string constructor for each array element. In the way, when the delete operator is used on an array, it calls a destructor for each array element and then calls operator delete[] to deallocate the memory.

Why overload new and delete?

The most common reason to overload new and delete are simply to check for memory leaks, and memory usage stats. Note that "memory leak" is usually generalized to memory errors. You can check for things such as double deletes and buffer overruns.

What happens when you overload the new and delete operator?

The compiler will remember how many elements were allocated, and call the destructor for each list correctly. The point of overloading the new and delete operator is to provide custom memory allocation strategy. For example, you could preallocate memory, and then take from that pool, instead of allocating everytime again memory from the OS.

What are overloaded operators in C++?

These overloaded operators are applied to a particular class, which has operator new () and operator delete (); global operators new and delete (not overloaded). These operators are used for classes that do not contain operator functions that overload the new and delete operators.

Why do we reload the delete operator in Java?

The new operator must be reloaded in cases where memory is allocated in non-standard way. Accordingly, the delete operator should free this memory in a non-standard way. As a rule, if the operator new is overloaded in a class, then the operator delete is also overloaded in this class.

What is the difference between the new and delete operator?

The new operator requests for the memory allocation in heap. If the sufficient memory is available, it initializes the memory to the pointer variable and returns its address. The delete operator is used to deallocate the memory.


3 Answers

No, you don't need to write all variations of the new and delete operators for your class.

There are multiple reasons to prefer some versions of new and delete over others. I will describe each reason separately.

Almost always prefer the delete operators that have a size parameter over those without one.

When I write delete operators for a base class that provides memory handling for other classes, I use these versions of the delete operators

void T::operator delete  ( void* ptr, std::size_t sz );
void T::operator delete[]( void* ptr, std::size_t sz );
void T::operator delete  ( void* ptr, std::size_t sz, std::align_val_t al ); // (since C++17)
void T::operator delete[]( void* ptr, std::size_t sz, std::align_val_t al ); // (since C++17)

and deliberately omit or =delete these versions.

void T::operator delete  ( void* ptr );
void T::operator delete[]( void* ptr );
void T::operator delete  ( void* ptr, std::align_val_t al ); // (since C++17)
void T::operator delete[]( void* ptr, std::align_val_t al ); // (since C++17)

The reason is that the std::size_t sz parameter tells me the size of the object or size of the array. I can't know the sizes of the derived class' objects when I write my base class, so using the size parameter helps. Some of my memory handlers segregate the objects by size (easier to pool memory when all the chunks are the same size). I can use the size parameter to quickly choose which memory pool to search, rather than searching all of them. That turns a O(n) algorithm into a O(1) action.

Some of my memory allocators use a "chain model" instead of a "block model", and the size parameter helps for deleting there too. (I call a memory allocator a "block model" if it preallocates a huge chunk and then partitions the chunk into separate blocks like an array. I call a memory handler a "chain model" if each chunk points to previous and next chunks like a linked list or chain.) So when somebody deletes a chunk from a chain of memory chunks, I want the delete operator to know the chunk being deleted is the correct size. I can put an assertion in the delete operation that asserts (size == address of next chunk - address of this chunk).

Where appropriate, prefer the new and delete operators with the alignment parameter.

Now that C++17 provides an alignment parameter for new operators, use them if you need them. If you need performance, align your objects on 4, 8, or 16 byte boundaries, do so! It makes the program a little faster.

So let's say you have an alignment-aware memory allocator. It knows that some objects are best stored on 4 byte boundaries because those objects are small and you can squeeze more into memory if you use 4 byte boundaries. It also knows some objects are best aligned on 8 byte boundaries because those objects are used often.

Your memory handler will know this if it provides the correct new operators and derived classes provide the correct values for alignments.

The 2017 C++ Standard says:

When allocating objects and arrays of objects whose alignment exceeds STDCPP_DEFAULT_NEW_ALIGNMENT, overload resolution is performed twice: first, for alignment-aware function signatures, then for alignment-unaware function signatures. This means that if a class with extended alignment has an alignment-unaware class-specific allocation function, it is the function that will be called, not the global alignment-aware allocation function. This is intentional: the class member is expected to know best how to handle that class.

This means the compiler will check for new and delete operators with the alignment parameter, and then check for operators without the alignment parameter.

If you have an alignment-aware memory handler, then always provide these new operators, even if you also want to give your client code the option of ignoring alignment.

void* T::operator new  ( std::size_t count, std::align_val_t al ); // (since C++17)
void* T::operator new[]( std::size_t count, std::align_val_t al ); // (since C++17)
void* T::operator new  ( std::size_t count,
    std::align_val_t al, user-defined-args... ); // (since C++17)
void* T::operator new[]( std::size_t count,
     std::align_val_t al, user-defined-args... ); // (since C++17)

You can force code to provide the alignment parameter if you provide the above new operators and omit or =delete these overloads.

void* T::operator new  ( std::size_t count );
void* T::operator new[]( std::size_t count );

void* T::operator new  ( std::size_t count, user-defined-args... );
void* T::operator new[]( std::size_t count, user-defined-args... );

Use class specific placement-new operators to provide hints.

Let's say you wrote a class that allocates several data members, and you want all those data members to be located on the same memory page. If the data is spread across several memory pages, the CPU will have to load different memory pages into the L1 or L2 cache just so you can access the member data for an object. If your memory handler can place all of an object's data members onto the same page, then your program will run faster because the CPU will not need to load multiple pages into cache.

These are the class specific placement new operators.

void* T::operator new  ( std::size_t count, user-defined-args... );
void* T::operator new[]( std::size_t count, user-defined-args... );
void* T::operator new  ( std::size_t count,
    std::align_val_t al, user-defined-args... ); // (since C++17)
void* T::operator new[]( std::size_t count,
     std::align_val_t al, user-defined-args... ); // (since C++17)

Overload them to look like this by providing a hint parameter.

void* T::operator new  ( std::size_t count, void* hint );
void* T::operator new[]( std::size_t count, void* hint );
void* T::operator new  ( std::size_t count, std::align_val_t al, void* hint ); // (since C++17)
void* T::operator new[]( std::size_t count, std::align_val_t al, void* hint ); // (since C++17)

The hint parameter tells the memory handler to try to place the object not at the location of that hint address, but on the same page as the hint address.

Now you can write a class that looks like this which is derived from your memory handling class.

class Foo : public MemoryHandler
{
public:
    Foo();
    ...
private:
    Blah * b_;
    Wham * f_;
};

Foo::Foo() : b_( nullptr ), f_( nullptr )
{
    // This should put data members on the same memory page as this Foo object.
    b_ = new ( this ) Blah;
    f_ = new ( this ) Wham;
}
like image 77
RichS Avatar answered Oct 20 '22 00:10

RichS


You only need to overload the versions of new and delete that you use. According to the example in [class.free], defining an operator new function in a class will hide all the global operator new functions. This is the same as defining a method with the same name as a base class function or global function hides the base or global versions.

Note that operator new and operator new[] are different names, so overloading operator new by itself will not hide the global operator new[] functions.

like image 21
1201ProgramAlarm Avatar answered Oct 19 '22 22:10

1201ProgramAlarm


If I write a C++ class with new and delete operators, do I need to overload all of those?

No you don't need to overload all of them. At a minimum, you need to overload the operators you need to customize.

I think we can assume you are doing something specific in the overloaded operators, else you wouldn't have needed them anyway.

The question becomes more should I overload all of these?

Yes, you probably should. It would be surprising if the the code did completely different things depending on the form of the new or delete used in the code, e.g.

auto* obj1 = new Obj{};
// vs
auto* obj2 = new Obj[5];

If the new operator has some special initialization applied, it would reasonably be expected that both forms would do that initialization.

A flip side to this is that if the other forms are not applicable, then favor deleting (= delete) those overloads entirely.

The C++ operators come in "sets", arithmetic, stream insertions and extractions, relational etc. It is common practice that when one of the operators in a set is overloaded, the others are as well.

It doesn't always apply, but generally does. E.g. concatenation operations often have the operator+ and operator+=, but not the operator- and operator-=

like image 30
Niall Avatar answered Oct 19 '22 22:10

Niall