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
u
can, upon request, transfer ownership to another unique pointeru2
. Upon completion of such a transfer, the following postconditions hold:
(4.1) —u2.p
is equal to the pre-transferu.p
,
(4.2) —u.p
is equal tonullptr
, and
(4.3) — if the pre-transferu.d
maintained state, such state has been transferred tou2.d
.
As in the case of a reset,u2
must 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