Let's say I have a class.
class BigData {...};
typedef boost::shared_ptr<BigData> BigDataPtr;
Then I do:
BigDataPtr bigDataPtr(new BigData());
Later on after I am done with my object and I am sure there no other users for the object.
Is it safe to do the following:
bigDataPtr->~BigDataPtr();
new (&*bigDataPtr) BigData;
Would this let me reset the object without any additional allocations?
Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.
All the instances point to the same object, and share access to one "control block" that increments and decrements the reference count whenever a new shared_ptr is added, goes out of scope, or is reset. When the reference count reaches zero, the control block deletes the memory resource and itself.
std::shared_ptr is a smart pointer that retains shared ownership of an object through a pointer. Several shared_ptr objects may own the same object.
So the best way to return a shared_ptr is to simply return by value: shared_ptr<T> Foo() { return shared_ptr<T>(/* acquire something */); }; This is a dead-obvious RVO opportunity for modern C++ compilers. I know for a fact that Visual C++ compilers implement RVO even when all optimizations are turned off.
There are a few ways to go about this. You can use placement new, and this is guaranteed to be safe for two reasons:
You have already allocated the memory for the object, so you know it’s sized and aligned correctly.
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.
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.
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With