Are there any valid use cases to use new and delete, raw pointers or c-style arrays with modern C++?
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
I have a dream ...
I'm dreaming of so called C++ courses/classes/curriculae will stop teaching (requiring) their students to use: ...
Since C++11 as established standard we have the Dynamic memory management facilities aka smart pointers.
Even from earlier standards we have the c++ standard Containers library as a good replacement for raw arrays (allocated with new T[]
) (notably usage of std::string
instead of c-style NUL
terminated character arrays).
Question(s) in bold:
Let aside the placement new
override, is there any valid use case that can't be achieved using smart pointers or standard containers but only using new
and delete
directly (besides implementation of such container/smart pointer classes of course)?
It's sometimes rumored (like here or here) that using new
and delete
handrolled can be "more efficient" for certain cases. Which are these actually? Don't these edge cases need to keep track of the allocations the same way as standard containers or smart pointers need to do?
Almost the same for raw c-style fixed size arrays: There is std::array
nowadays, which allows all kinds of assignment, copying, referencing, etc. easily and syntactically consistent as expected by everyone. Are there any use cases to choose a T myArray[N];
c-style array in preference of std::array<T,N> myArray;
?
Regarding interaction with 3rd party libraries:
Assumed a 3rd party library returns raw pointers allocated with new
like
MyType* LibApi::CreateNewType() {
return new MyType(someParams);
}
you can always wrap that to a smart pointer to ensure that delete
is called:
std::unique_ptr<MyType> foo = LibApi::CreateNewType();
even if the API requires you to call their legacy function to free the resource like
void LibApi::FreeMyType(MyType* foo);
you still can provide a deleter function:
std::unique_ptr<MyType, LibApi::FreeMyType> foo = LibApi::CreateNewType();
I'm especially interested in valid "every day" use cases in contrast to academic/educational purpose requirements and restrictions, which aren't covered by the mentioned standard facilities.
That new
and delete
may be used in memory management / garbage collector frameworks or standard container implementation is out of question1.
One major motivation ...
... to ask this question is to give an alternative approach vs any (homework) questions, which are restricted to use any of the constructs mentioned in the title, but serious questions about production ready code.
These are often referred to as the basics of memory management, which is IMO blatantly wrong/misunderstood as suitable for beginners lectures and tasks.
1)Add.: Regarding that paragraph, this should be a clear indicator that new
and delete
isn't for beginner c++ students, but should be left for the more advanced courses.
When ownership should not be local.
As an example, a pointer container may not want ownership over the pointers in it to reside in the pointers themselves. If you try to write a linked list with forward unique ptrs, at destruction time you can easily blow the stack.
A vector
-like container of owning pointers may be better suited to storing delete operation at the container or subcontainer level, and not at the element level.
In those and similar cases, you wrap ownership like a smart pointer does, but you do it at a higher level. Many data structures (graphs, etc) may have similar issues, where ownership properly resides at a higher point than where the pointers are, and they may not map directly to an existing container concept.
In some cases it may be easy to factor out the container-ownership from the rest of the data structure. In others it may not.
Sometimes you have insanely complex non-local non-reference counted lifetimes. There is no sane spot to put the ownership pointer in those cases.
Determining correctness here is hard, but not impossible. Programs that are correct and have such complex ownership semantics exist.
All of these are corner cases, and few programmers should run into them more than a handful of times in a career.
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.
Some APIs might expect you to create objects with new
but will take over ownership of the object. The Qt library for example has a parent-child model where the parent deletes its children. If you use a smart pointer, you are going to run into double-deletion issues if you're not careful.
Example:
{
// parentWidget has no parent.
QWidget parentWidget(nullptr);
// childWidget is created with parentWidget as parent.
auto childWidget = new QWidget(&parentWidget);
}
// At this point, parentWidget is destroyed and it deletes childWidget
// automatically.
In this particular example, you can still use a smart pointer and it will be fine:
{
QWidget parentWidget(nullptr);
auto childWidget = std::make_unique<QWidget>(&parentWidget);
}
because objects are destroyed in reverse order of declaration. unique_ptr
will delete childWidget
first, which will make childWidget
unregister itself from parentWidget
and thus avoid double-deletion. However, most of the time you don't have that neatness. There are many situations where the parent will be destroyed first, and in those cases, the children will get deleted twice.
In the above case, we own the parent in that scope, and thus have full control of the situation. In other cases, the parent might not be hours, but we're handing ownership of our child widget to that parent, which lives somewhere else.
You might be thinking that to solve this, you just have to avoid the parent-child model and create all your widgets on the stack and without a parent:
QWidget childWidget(nullptr);
or with a smart pointer and without a parent:
auto childWidget = std::make_unique<QWidget>(nullptr);
However, this will blow up in your face too, since once you start using the widget, it might get re-parented behind your back. Once another object becomes the parent, you get double-deletion when using unique_ptr
, and stack deletion when creating it on the stack.
The easiest way to work with this is to use new
. Anything else is either inviting trouble, or more work, or both.
Such APIs can be found in modern, non-deprecated software (like Qt), and have been developed years ago, long before smart pointers were a thing. They cannot be changed easily since that would break people's existing code.
The OP specificly asks about how/when handrolling will be more efficient in an everyday use case - and I will address that.
Assuming a modern day compiler/stl/platform, there is not an every day use where handrolled use of new and delete will be more efficient. For the shared_ptr case i believe it will be marginal. In an extremely tight loop(s) there could be something to gain by just using raw new to avoid the ref counting (and find some other method of cleaning up - unless somehow imposed on you, you choose to use shared_ptr for a reason), but that is not an everyday or common example. For the unique_ptr there is not actually any difference, so i think it is safe to say that it is more of rumour and folklore and that performance wise it will not actually matter at all (difference will not be measurable in normal cases).
There are cases where it is not desirable or possible to use a smart pointer class as already covered by others.