C++ multiple unique pointers from same raw pointer

后端 未结 3 1705
无人及你
无人及你 2021-02-20 12:27

Consider my code below. My understanding of unique pointers was that only one unique pointer can be used to reference one variable or object. In my code I have more than one uni

相关标签:
3条回答
  • 2021-02-20 12:33

    This example of code is a bit artificial. unique_ptr is not usually initialized this way in real world code. Use std::make_unique or initialize unique_ptr without storing raw pointer in a variable:

    unique_ptr <int> uniquePtr2(new int);
    
    0 讨论(0)
  • 2021-02-20 12:52

    Just an addition to Christian Hackl's excellent answer:

    std::unique_ptr was introduced to ensure RAII for pointers; this means, in opposite to raw pointers you don't have to take care about destruction yourself anymore. The whole management of the raw pointer is done by the smart pointer. Leaks caused by a forgotten delete can not happen anymore.

    If a std::unique_ptr would only allow to be created by std::make_unique, it would be absolutely safe regarding allocation and deallocation, and of course that would be also detectable during compile time.

    But that's not the case: std::unique_ptr is also constructible with a raw pointer. The reason is, that being able to be constructed with a hard pointer makes a std::unique_ptr much more useful. If this would not be possible, e.g. the pointer returned by Christian Hackl's function_with_runtime_input() would not be possible to integrate into a modern RAII environment, you would have to take care of destruction yourself.

    Of course the downside with this is that errors like yours can happen: To forget destruction is not possible with std::unique_ptr, but erroneous multiple destructions are always possible (and impossible to track by the compiler, as C.H. already said), if you created it with a raw pointer constructor argument. Always be aware that std::unique_ptr logically takes "ownership" of the raw pointer - what means, that no one else may delete the pointer except the one std::unique_ptr itself.

    As rules of thumb it can be said:

    • Always create a std::unique_ptr with std::make_unique if possible.
    • If it needs to be constructed with a raw pointer, never touch the raw pointer after creating the std::unique_ptr with it.
    • Always be aware, that the std::unique_ptr takes ownership of the supplied raw pointer
    • Only supply raw pointers to the heap. NEVER use raw pointers which point to local stack variables (because they will be unavoidably destroyed automatically, like valin your example).
    • Create a std::unique_ptr only with raw pointers, which were created by new, if possible.
    • If the std::unique_ptr needs to be constructed with a raw pointer, which was created by something else than new, add a custom deleter to the std::unique_ptr, which matches the hard pointer creator. An example are image pointers in the (C based) FreeImage library, which always have to be destroyed by FreeImage_Unload()

    Some examples to these rules:

    // Safe
    std::unique_ptr<int> p = std::make_unique<int>();
    
    // Safe, but not advisable. No accessible raw pointer exists, but should use make_unique. 
    std::unique_ptr<int> p(new int());
    
    // Handle with care. No accessible raw pointer exists, but it has to be sure
    // that function_with_runtime_input() allocates the raw pointer with 'new'
    std::unique_ptr<int> p( function_with_runtime_input() );
    
    // Safe. No accessible raw pointer exists,
    // the raw pointer is created by a library, and has a custom
    // deleter to match the library's requirements
    struct FreeImageDeleter {
        void operator() (FIBITMAP* _moribund) { FreeImage_Unload(_moribund); }
    };
    std::unique_ptr<FIBITMAP,FreeImageDeleter> p( FreeImage_Load(...) );
    
    // Dangerous. Your class method gets a raw pointer
    // as a parameter. It can not control what happens
    // with this raw pointer after the call to MyClass::setMySomething()
    // - if the caller deletes it, your'e lost.
    void MyClass::setMySomething( MySomething* something ) {
       // m_mySomethingP is a member std::unique_ptr<Something>
       m_mySomethingP = std::move( std::unique_ptr<Something>( something ));
    }
    
    // Dangerous. A raw pointer variable exists, which might be erroneously
    // deleted multiple times or assigned to a std::unique_ptr multiple times.
    // Don't touch iPtr after these lines!
    int* iPtr = new int();
    std::unique_ptr<int> p(iPtr);
    
    // Wrong (Undefined behaviour) and a direct consequence of the dangerous declaration above.
    // A raw pointer is assigned to a std::unique_ptr<int> twice, which means
    // that it will be attempted to delete it twice.
    // This couldn't have happened if iPtr wouldn't have existed in the first
    // place, like shown in the 'safe' examples.
    int* iPtr = new int();
    std::unique_ptr<int> p(iPtr);
    std::unique_ptr<int> p2(iPtr);
    
    
    // Wrong. (Undefined behaviour)
    // An unique pointer gets assigned a raw pointer to a stack variable.
    // Erroneous double destruction is the consequence
    int val;
    int* valPtr = &val;
    std::unique_ptr<int> p(valPtr);
    
    0 讨论(0)
  • 2021-02-20 12:56

    But still, why is this valid

    It is not valid! It's undefined behaviour, because the destructor of std::unique_ptr will free an object with automatic storage duration.

    Practically, your program tries to destroy the int object three times. First through uniquePtr2, then through uniquePtr1, and then through val itself.

    and not having a compilation error?

    Because such errors are not generally detectable at compile time:

    unique_ptr <int> uniquePtr1(valPtr);
    unique_ptr <int> uniquePtr2(function_with_runtime_input());
    

    In this example, function_with_runtime_input() may perform a lot of complicated runtime operations which eventually return a pointer to the same object valPtr points to.


    If you use std::unique_ptr correctly, then you will almost always use std::make_unique, which prevents such errors.

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