As far as I see the code below is correct:
#include <iostream>
#include <functional>
struct A
{
std::function<void ()> func;
int value = 5;
~A() { func(); }
};
int main()
{
A* p_a = new A();
p_a->func = [&p_a]() { std::cout << p_a->value; };
delete p_a;
return 0;
}
but the following code is not and results in a segmentation fault:
#include <memory>
int main()
{
std::unique_ptr<A> p_a = std::make_unique<A>();
p_a->func = [&p_a]() { std::cout << p_a->value; };
p_a = {};
return 0;
}
std::unique_ptr clears its internal pointer first and then deletes the object and thus prevents access to the object being destroyed.
What is it for? What is the logic behind this?
EDIT1:
Marked the question is a duplicate. I would prefer std::uniqu_ptr to keep old object if the deleter throws an exception (if I did not misunderstand something).
This implementation is done for preventing recursion in std::unique_ptr::reset().
When you take std::unique_ptr by reference, you don't own std::unique_ptr and don't control the life-time of the referenced object. The correct code must always check for nullptr the raw pointer.
int main()
{
std::unique_ptr<A> p_a = std::make_unique<A>();
p_a->func = [&p_a]() { if (p_a) std::cout << p_a->value; };
p_a = {};
return 0;
}
This is just a comment, but too long to fit into the comment section. So I post it as an answer.
First lets look at the standard, it says
ucan, upon request, transfer ownership to another unique pointeru2. Upon completion of such a transfer, the following postconditions hold:
(4.1) —u2.pis equal to the pre-transferu.p,
(4.2) —u.pis equal tonullptr, and
(4.3) — if the pre-transferu.dmaintained state, such state has been transferred tou2.d.
As in the case of a reset,u2must properly dispose of its pre-transfer owned object via the pre-transfer associated deleter before the ownership transfer is considered complete.
The standard only specified the behavior after the reset, the exact behavior during reset is implementation-dependent.
Then let's look at the GNU implementation:
void reset(pointer __p) noexcept
{
const pointer __old_p = _M_ptr();
_M_ptr() = __p;
if (__old_p)
_M_deleter()(__old_p);
}
~unique_ptr() noexcept
{
static_assert(...);
auto& __ptr = _M_t._M_ptr();
if (__ptr != nullptr)
get_deleter()(std::move(__ptr));
__ptr = pointer();
}
Which looks quite odd because the exact order are different in reset and dtor. It doesn't make sense to me, until I read the MSVC implementation:
void reset(pointer _Ptr = nullptr) noexcept {
pointer _Old = _STD exchange(_Mypair._Myval2, _Ptr);
if (_Old) {
_Mypair._Get_first()(_Old);
}
}
When reset, of course we need to swap current pointer with the argument. Also, don't forget to call the deleter. Simple job.
~unique_ptr() noexcept {
if (_Mypair._Myval2) {
_Mypair._Get_first()(_Mypair._Myval2);
}
}
In destructor, Microsoft doesn't bother set the pointer to nullptr, they just call the deleter. Simple.
If you really want to set the pointer to nullptr, I bet you will set it after you call the deleter.
Conclusion:
I don't think the STL authors really want to "prevent" you doing something. They just simply write the most straight-forward code to implement the requirement.
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