Reputation: 1828
I'm going through some tutorials on how smart pointers work in C++, but I'm stuck on the first one I tried: the unique pointer. I'm following guidelines from wikipedia, cppreference and cplusplus. I've also looked at this answer already. A unique pointer is supposed to be the only pointer that has ownership over a certain memory cell/block if I understood this correctly. This means that only the unique pointer (should) point to that cell and no other pointer. From wikipedia they use the following code as an example:
std::unique_ptr<int> p1(new int(5));
std::unique_ptr<int> p2 = p1; //Compile error.
std::unique_ptr<int> p3 = std::move(p1); //Transfers ownership. p3 now owns the memory and p1 is rendered invalid.
p3.reset(); //Deletes the memory.
p1.reset(); //Does nothing.
Until the second line, that worked fine for me when I test it. However, after moving the first unique pointer to a second unique pointer, I find that both pointers have access to the same object. I thought the whole idea was for the first pointer to be rendered useless so to speak? I expected a null pointer or some undetermined result. The code I ran:
class Figure {
public:
Figure() {}
void three() {
cout << "three" << endl;
}
};
class SubFig : public Figure {
public:
void printA() {
cout << "printed a" << endl;
}
};
int main()
{
unique_ptr<SubFig> testing (new SubFig());
testing->three();
unique_ptr<SubFig> testing2 = move(testing);
cout << "ok" << endl;
int t;
cin >> t; // used to halt execution so I can verify everything works up til here
testing->three(); // why is this not throwing a runtime error?
}
Here, testing
has been moved to testing2
, so I'm surprised to find I can still call the method three()
on testing
.
Also, calling reset() doesn't seem to delete the memory like it said it would. When I modify the main method to become:
int main()
{
unique_ptr<SubFig> testing (new SubFig());
testing->three();
unique_ptr<SubFig> testing2 = move(testing);
cout << "ok" << endl;
int t;
cin >> t;
testing.reset(); // normally this should have no effect since the pointer should be invalid, but I added it anyway
testing2.reset();
testing2->three();
}
Here I expect three()
not to work for testing2
since the example from wikipedia mentioned the memory should be deleted by resetting. I'm still printing out printed a as if everything is fine. That seems weird to me.
So can anyone explain to me why:
reset()
is called?Upvotes: 4
Views: 2282
Reputation:
Essentially you invoke a member function through a null pointer:
int main()
{
SubFig* testing = nullptr;
testing->three();
}
... which is undefined behavior.
From 20.8.1 Class template unique_ptr (N4296)
4 Additionally, u can, upon request, transfer ownership to another unique pointer u2. Upon completion of such a transfer, the following postconditions hold:
- u2.p is equal to the pre-transfer u.p,
- u.p is equal to nullptr, and
- if the pre-transfer u.d maintained state, such state has been transferred to u2.d.
(emphasis mine)
Upvotes: 8
Reputation: 48635
After the std::move()
the original pointer testing
is set to nullptr
.
The likely reason std::unique_ptr
doesn't check for null access to throw a runtime error is that it would slow down every time you used the std::unique_ptr
. By not having a runtime check the compiler is able to optimize the std::unique_ptr
call away entirely, making it just as efficient as using a raw pointer.
The reason you didn't get a crash when calling the nullptr
is likely because the function you called doesn't access the (non-existent) object's memory. But it is undefined behavior so anything could happen.
Upvotes: 8
Reputation: 1445
On calling std::unique_ptr<int> p3 = std::move(p1);
your original pointer p1
is in undefined state, as such using it will result in undefined behavior. Simply stated, never ever do it.
Upvotes: 2