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:02

I think this is typically a good use case and/or guideline to follow:

  • When the pointer is local to single function's scope.
  • The dynamic memory is handled in the function and you need the heap.
  • You are not passing the pointer around and it isn't leaving the functions scope.

PSEUDO Code:

#include <SomeImageLibrary>

// Texture is a class or struct defined somewhere else.
unsigned funcToOpenAndLoadImageData( const std::string& filenameAndPath, Texture& texture, some optional flags (how to process or handle within function ) {
    // Depending on the above library: file* or iostream...

    // 1. OpenFile

    // 2. Read In Header

    // 3. Process Header

    // 4. setup some local variables.

    // 5. extract basic local variables from the header
    //    A. texture width, height, bits per pixel, orientation flags, compression flags etc.

    // 6. Do some calculations based on the above to find out how much data there is for the actual ImageData...

    // 7. Raw pointer (typically of unsigned char).

    // 8. Create dynamic memory for that pointer or array.

    // 9. Read in the information from the file of that amount into the pointer - array.

    // 10. Verify you have all the information.

    // 11. Close the file handle.

    // 12. Process some more information on the actual pointer or array itself
    // based on its orientation, its bits per pixel, its dimensions, the color type, the compression type, and or if it exists encryption type.

    // 13. Store the modified data from the array into Your Structure (Texture - Class/Struct).

    // 14. Free up dynamic memory...

    // 15. typically return the texture through the parameter list as a reference

    // 16. typically return an unsigned int as the Texture's numerical ID.    
}

This is quite effective; efficient, doesn't need any use of smart pointers; is fast especially if inlining the function. This type of function can either be a stand alone or even a member of a class. If a pattern follows this then it is quite safe to use new & delete or new[] & delete[] if done properly.

EDIT

In the mentioned case(s) above sometimes you want the raw pointers and you want it on the heap. Let's say you have an application that will load say 5,000 texture files, 500 model files, 20 scene files, 500-1000 audio files. You do not want your loading time to be slow, you also want it to be "cache" friendly. Texture loading is very good example of having the pointer on the heap as opposed to the functions stack because the texture could be large in size exceeding your local memory capabilities.

In this context you will be calling these load functions once per object, but you will be calling them several times. After you loaded & created your resources or assets and stored them internally is when and where you would want to use containers instead of arrays and smart pointers instead of raw pointers.

You will load a single asset once, but you may have 100s or 1000s of instances of it. It is with these instances that you would prefer the use of containers and the use of smart pointers to manage their memory within your application over raw pointers and arrays. The initial loading is where you would prefer to be closer to the metal without all the extra unwanted overhead.

If you were working on a A+ class game and you could save your audience 15 to 30s or more of loading time per loading screen then you are in the winners circle. Yes care does need to be taken and yes you can still have unhandled exceptions, but no code is 100% full proof.

This type of design is rarely prone to memory leaks except for those exceptions which can still be handled in many of the cases. Also to safely manage raw pointers, preprocessor macros work well for easy clean up.

Many of these library types also work and deal with raw data, raw memory allocation, etc. and many times smart pointers don't necessarily fit these types of jobs.

查看更多
初与友歌
3楼-- · 2018-12-31 09:03

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.

查看更多
泛滥B
4楼-- · 2018-12-31 09:03

You sometimes have to call new when using private constructors.

Say you decide to have a private constructor for a type that is intended to be called by a friend factory or an explicit create method. You can call new inside this factory but make_unique won't work.

查看更多
一个人的天荒地老
5楼-- · 2018-12-31 09:05

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.

查看更多
人气声优
6楼-- · 2018-12-31 09:05

Adding to other answers, there are some cases where new/delete make sense -

  1. Integrating with a 3rd party library which returns the raw pointer and expect you return the pointer to the library once you are done (The library has its own memory management functionality).
  2. Working on resource constrained embedded device where memory (RAM/ROM) is a luxury (even a few kilobytes). Are you sure you want to add more runtime (RAM) and compiled (ROM/Overlay) memory requirement to your application or you want to program carefully with new/delete?
  3. From purist point of view, in some cases smart pointers won't work intuitively (due to their nature). For example, for builder pattern you should to use reinterpret_pointer_cast, if you are using smart pointers. Another case is where you need to cast from a base type to a derived type. You put yourself danger if you get the raw pointer from smart pointer, cast it and put it in another smart pointer and ended up freeing the pointer multiple times.
查看更多
不流泪的眼
7楼-- · 2018-12-31 09:09

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;
}
查看更多
登录 后发表回答