Can I use placement new to reset an object within a shared_ptr?

后端 未结 4 958
不思量自难忘°
不思量自难忘° 2020-12-16 22:54

Let\'s say I have a class.

class BigData {...};
typedef boost::shared_ptr BigDataPtr; 

Then I do:

BigDataPt         


        
相关标签:
4条回答
  • 2020-12-16 23:29

    It is safe if BigData constructor and destructor do not throw exceptions and bigDataPtr is not shared between threads and no pointers or references exist to dynamically allocated members of BigData (if any).

    If the destructor throws an exception you may end up with a partially destroyed object (throwing destructors are not generally recommended and standard containers require that destructors of elements do not throw).

    If the constructor throws you may end up destroying the object but not constructing a new one.

    If bigDataPtr is shared between threads that may also lead to a race condition unless a locking discipline is used.

    If code elsewhere takes references or pointers to dynamically allocated members of BigData, when it creates a new BigData its dynamically allocated members may be allocated at other addresses, so existing pointers and references to the members become invalid.

    If you are concerned with dubious dereference in new (&*bigDataPtr) BigData; statement use a plain pointer instead:

    BigData* p = bigDataPtr.get();
    p->~BigData();
    new (p) BigData;
    
    0 讨论(0)
  • 2020-12-16 23:33

    Yes it is normally safe. (Nod to Maxim Yegorushkin's observation about a throwing edge case)

    Note the typo message below

    Boost defines the dereference and -> operators as

    template<class T>
    typename boost::detail::sp_dereference< T >::type boost::shared_ptr< T >::operator* () const;
    
    template<class T>
    typename boost::detail::sp_member_access< T >::type boost::shared_ptr< T >::operator-> () const;
    

    When those detail bits are resolved, you have this

    template<class T>
    T & boost::shared_ptr< T >::operator* () const
    
    template<class T>
    T * boost::shared_ptr< T >::operator-> () const 
    

    So you are dealing with the pointed-to object directly. There are no proxies or other constructs that may interfere with what you're attempting.

    As the pointed-to data is concerned, your code:

    bigDataPtr->~BigDataPtr();
    new (&*bigDataPtr) BigData;
    

    May have a typo. But if you intended:

    bigDataPtr->~BigData();
    new (&*bigDataPtr) BigData;
    

    It will resolve to

    (BigData pointer)->~BigData();
    new (&(BigData reference)) BigData;
    

    This is legal, and you are correct that it would avoid the additional allocation normally incurred with an assignment.

    0 讨论(0)
  • 2020-12-16 23:42

    Firstly, if the constructor throws and the class is not trivially destructible then you have a problem, since the shared_ptr "wants" to delete it, which would provoke UB.

    So you must deal with that, either by using a nothrow constructor or by catching any exception and preventing the smart pointer from deleting the object. Since shared_ptr doesn't have a release() function, that's easier said than done. You could call terminate() if all else fails, but that won't make you popular with your users.

    If there are no other references to the object, then it will work provided that the class has no const or reference non-static data members (including members-of-members). The reason is 3.8/7:

    If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object ... can be used to manipulate the new object, if ... the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type ...

    Note that the shared_ptr holds just such a pointer, which it will use to manipulate the new object, which is UB if any of the conditions in 3.8/7 is broken. The only one that might be broken is this one, you've covered the rest with what you've said about your code. In particular, it's required that you created the original object as an instance of BigData, not a class derived from BigData, because the new object is required to have the same most-derived type as the old one.

    There are usually more robust ways to reset an object than this. For example, implement operator= (copy- or move assignment operator) and then write *bigDataPtr = BigData(). Of course that might not be quite as fast.

    0 讨论(0)
  • 2020-12-16 23:48

    There are a few ways to go about this. You can use placement new, and this is guaranteed to be safe for two reasons:

    1. You have already allocated the memory for the object, so you know it’s sized and aligned correctly.

    2. shared_ptr is non-invasive; its sole responsibility is to count references and call the deleter when necessary.

    However, consider what can happen if reconstruction of the object fails—i.e., throws an exception:

    bigDataPtr->~BigDataPtr();
    new (bigDataPtr.get()) BigData;
    

    Then you have a problem: the deleter can be called on a non-constructed object, leading almost certainly to undefined behaviour. I say “almost” because the deleter could be a no-op, in which case all would be well.

    Safer, I think, would be to move a new value into the existing object:

    *bigDataPtr = BigData(42);
    

    Or add a reset() member function to BigData:

    bigDataPtr->reset(42);
    

    Then it’s explicit what your real intent is, and you don’t need to be as concerned about object lifetimes.

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