Are there any valid use cases to use new and delete, raw pointers or c-style arrays with modern C++?

前端 未结 19 904
梦谈多话
梦谈多话 2020-11-22 07:20

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

相关标签:
19条回答
  • 2020-11-22 07:59

    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.

    0 讨论(0)
  • 2020-11-22 08:01

    When you have to pass something across the DLL boundary. You (almost) can't do that with smart pointers.

    0 讨论(0)
  • 2020-11-22 08:01

    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.

    0 讨论(0)
  • 2020-11-22 08:02

    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.

    0 讨论(0)
  • 2020-11-22 08:03

    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.
    0 讨论(0)
  • 2020-11-22 08:03

    When you want to create multidimensional arrays but aren't familiar with C++11 syntax like std::move, or aren't familiar with writing custom deleters for smart pointers.

    0 讨论(0)
提交回复
热议问题