4

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:

  • moving from one unique pointer to another unique pointer doesn't make the first one invalid?
  • resetting does not actually remove the memory? What's actually happening when reset() is called?
Community
  • 1
  • 1
Babyburger
  • 1,730
  • 3
  • 19
  • 32

3 Answers3

8

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)

  • I see. I was under the impression that calling a method on a null pointer would result in an exception, like Java would when calling a method on a null reference. So it's also correct when I say calling `reset()` will turn *testing* into a null pointer and again the behavior is undefined? – Babyburger Jun 25 '16 at 10:39
  • @Babyburger Yes, reset is similar. –  Jun 25 '16 at 11:02
8

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.

Galik
  • 47,303
  • 4
  • 80
  • 117
2

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.

cplusplusrat
  • 1,435
  • 12
  • 27