Here\'s a notable video (Stop teaching C) about that paradigm change to take in teaching the c++ language.
And an also notable blog post
another example that has not already been mentioned is when you need to pass an object through a legacy (possibly asynchronous) C-callback. Usually, these things take a function pointer and a void* (or an opaque handle) to pass some payload upon. As long as the callback gives some guarantee on when/how/how many times it will be invoked, resorting to a plain new->cast->callback->cast->delete is the most straightforward solution (ok, the delete will be probably managed by a unique_ptr on callback site, but the bare new is still there). Of course, alternative solutions exist, but always requires the implementation of some sort of explicit/implicit 'object lifetime manager' in that case.
You can still use new
and delete
if we want to create our own lightweight memory allocation mechanism. For example
1.Using In-Place new : Generally used for allocating from preallocated memory;
char arr[4];
int * intVar = new (&arr) int; // assuming int of size 4 bytes
2.Using Class Specific Allocators : If we want a custom allocator for our own classes.
class AwithCustom {
public:
void * operator new(size_t size) {
return malloc(size);
}
void operator delete(void * ptr) {
free(ptr);
}
};
One of the problem I deal with is mining big data structures for hardware design and language analysis with few hundred million elements. Memory usage and performance is a consideration.
Containers are a good convenient way to quickly assemble data and work with it, but the implementation uses extra memory and extra dereferences which affect both, the memory and performance. My recent experiment with replacing smart pointers with a different custom implementation provided about 20% performance gain in a verilog preprocessor. Few years ago I did compare custom lists and custom trees vs vectors/maps and also saw gains. The custom implementations rely on regular new/delete.
So, new/delete are useful in high-efficiency applications for custom designed data structs.
The primary use case where I still use raw pointers is when implementing a hierarchy that uses covariant return types.
For example:
#include <iostream>
#include <memory>
class Base
{
public:
virtual ~Base() {}
virtual Base* clone() const = 0;
};
class Foo : public Base
{
public:
~Foo() override {}
// Case A in main wouldn't work if this returned `Base*`
Foo* clone() const override { return new Foo(); }
};
class Bar : public Base
{
public:
~Bar() override {}
// Case A in main wouldn't work if this returned `Base*`
Bar* clone() const override { return new Bar(); }
};
int main()
{
Foo defaultFoo;
Bar defaultBar;
// Case A: Can maintain the same type when cloning
std::unique_ptr<Foo> fooCopy(defaultFoo.clone());
std::unique_ptr<Bar> barCopy(defaultBar.clone());
// Case B: Of course cloning to a base type still works
std::unique_ptr<Base> base1(fooCopy->clone());
std::unique_ptr<Base> base2(barCopy->clone());
return 0;
}
I'm going to be contrarian, and go on record as saying "no" (at least to the question I'm pretty sure you really intended to ask, for most of the cases that have been cited).
What seem like obvious use-cases for using new
and delete
(e.g., raw memory for a GC heap, storage for a container) really aren't. For these cases, you want "raw" storage, not an object (or array of objects, which is what new
and new[]
provide respectively).
Since you want raw storage, you really need/want to use operator new
and operator delete
to manage the raw storage itself. You then use placement new
to create objects in that raw storage, and directly invoke the destructor to destroy the objects. Depending on the situation, you might want to use a level of indirection to that though--for example, the containers in the standard library use an Allocator class to handle these tasks. This is passed as a template parameter, which provides a customization point (e.g., a way to optimize allocation based on a particular container's typical usage pattern).
So, for these situations, you end up using the new
keyword (in both the placement new and the invocation of operator new
), but not something like T *t = new T[N];
, which is what I'm pretty sure you intended to ask about.
One valid use case is having to interact with legacy code. Especially if passing raw pointers to functions that take ownership of them.
Not all libraries you use may be using smart pointers and to use them you may need to provide or accept raw pointers and manage their lifetimes manually. This may even be the case within your own codebase if it has a long history.
Another use case is having to interact with C which does not have smart pointers.