Are there any valid use cases to use new and delet

2018-12-31 08:48发布

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.

标签: c++ c++11
19条回答
谁念西风独自凉
2楼-- · 2018-12-31 09:11

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.

查看更多
零度萤火
3楼-- · 2018-12-31 09:13

3 common examples where you have to use new instead of make_...:

  • If your object doesn't have a public constructor
  • If you want to use a custom deleter
  • If you are using c++11 and want to create an object that is managed by a unique_ptr (athough I'd recommend writing your own make_unique in that case).

In all those cases however, you'd directly wrap the returned pointer into a smart pointer.

2-3 (probably not so common) examples, where you wouldn't want/can't to use smart pointers:

  • If you have to pass your types through a c-api (you are the one implementing create_my_object or implement a callback that has to take a void*)
  • Cases of conditional ownership: Think of a string, that doesn't allocate memory when it is created from a string litteral but just points to that data. Nowerdays you probably could use a std::variant<T*, unique_ptr<T>> instead, but only if you are ok with the the information about the ownership being stored in the variant and is you accept the overhead of checking which member is active for each access. Of course this is only relevant if you can't/don't want to afford the overhead of having two pointers (one owning and one non-owning)
    • If you want to base your ownership on anything more complex than a pointer. E.g. you want to use a gsl::owner so you can easily query it's size and have all the other goodies (iteration, rangecheck...). Admittedly, you'd most likely wrap that in your own class,so this might fall into the category of implementing a container.
查看更多
低头抚发
4楼-- · 2018-12-31 09:14

There is still a chance to use malloc/free in C++, as you can use new/delete, and anything higher level wrapping the STL memory templates provided.

I think in order to really learn C++ and especially understand the C++11 memory templates you should create simple structures with new and delete. Just to better understand how they work. All the smart pointer classes rely on those mechanisms. So if you understand what new and delete does, you are going to appreciate the template more and really find smart ways to use them.

Today I personally try to avoid them as much as possible, but one main reason is the performance, which you should care if it is critical.

These are my rules of thumb I always have in mind:

std::shared_ptr: Automatic management of pointers but due to the reference counting it uses for tracing the accessed pointers, you have a worse performance every time you access these objects. Compared simple pointers I would say 6 times slower. Keep in mind, you can use get() and extract the primitive pointer, and continue accessing it. Of you must be careful with that one. I like to as that as a reference with *get(), so the worse performance is not really a deal.

std::unique_ptr The pointer access may only happen at one point in the code. Because this template forbids copy, thanks to the r-references && feature, it is much faster than an std::shared_ptr. Because there is still some ownership overhead in this class I would say, they are about twice as slow as a primitive pointer. You access the object than the primitive pointer within that template. I also like to use reference trick here, for less required accesses to the object.

About performance, it might be true, that those templates are slower, but keep in mind that if you want to optimize software, you should profile first and see what really takes many instructions. It is very unlikely that smart-pointers are the problem, but sure it depends on your implementation.

In C++ no one should care about malloc and free, but they exist for legacy code. They differ basically in the fact, that they know nothing about c++ classes, which with new and delete operator case is different.

I use std::unique_ptr and std::shared_ptr in my project Commander Genius everywhere and I'm really happy that they exist. I have not to deal with memory leaks and segfaults since then. Before that, we had our own smart-pointer template. So for productive software, I cannot recommend them enough.

查看更多
栀子花@的思念
5楼-- · 2018-12-31 09:15

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.

查看更多
千与千寻千般痛.
6楼-- · 2018-12-31 09:19

Another use case may be 3rd party library returning raw pointer which is internally covered by own intrusive reference counting (or own memory management - which is not covered by any API/user interface).

Good example is OpenSceneGraph and their implementation of osg::ref_ptr container and osg::Referenced base class.

Although it may be possible to use shared_ptr, the intrusive reference counting is way better for scene graph like use cases.

Personally I do see anything "smart" on the unique_ptr. It is just scope locked new & delete. Although shared_ptr looks way better, it requires overhead which is in many practical cases unacceptable.

So in general my use case is:

When dealing with non-STL raw pointer wrappers.

查看更多
低头抚发
7楼-- · 2018-12-31 09:20

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.

查看更多
登录 后发表回答