4

First of all, I know that we should use std::make_unique() instead of calling the std::unique_ptr constructor and I know why.

But I was looking at the documentation of std::unique_ptr to pass the time and sharpen my knowledge about it and I found the following examples about the constructor usage:

// unique_ptr constructor example
#include <iostream>
#include <memory>

int main () {
  std::default_delete<int> d;
  std::unique_ptr<int> u1;
  std::unique_ptr<int> u2 (nullptr);
  std::unique_ptr<int> u3 (new int);
  std::unique_ptr<int> u4 (new int, d);
  std::unique_ptr<int> u5 (new int, std::default_delete<int>());
  std::unique_ptr<int> u6 (std::move(u5));
  std::unique_ptr<int> u7 (std::move(u6));
  std::unique_ptr<int> u8 (std::auto_ptr<int>(new int));

  std::cout << "u1: " << (u1?"not null":"null") << '\n';
  std::cout << "u2: " << (u2?"not null":"null") << '\n';
  std::cout << "u3: " << (u3?"not null":"null") << '\n';
  std::cout << "u4: " << (u4?"not null":"null") << '\n';
  std::cout << "u5: " << (u5?"not null":"null") << '\n';
  std::cout << "u6: " << (u6?"not null":"null") << '\n';
  std::cout << "u7: " << (u7?"not null":"null") << '\n';
  std::cout << "u8: " << (u8?"not null":"null") << '\n';

  return 0;
}

It generates (and I verified it by executing the code) the following results:

u1: null
u2: null
u3: not null
u4: not null
u5: null
u6: null
u7: not null
u8: not null

What I'm struggling to understand is:

  • Why is u4 valid while u5 is not (nullptr) ?
  • Why is u7 valid but not u6 ?

Perhaps these questions are very basic but I'm completely missing the point.

If anyone could enlighten me about these questions, I would be grateful.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Fareanor
  • 5,900
  • 2
  • 11
  • 37
  • 1
    `u5` died while initializing `u6`, and *then* `u6` died while initializing `u7`. `std::unique_ptr` will become null when moved from, by design, to enforce its guarantee of unique ownership. – Quentin Sep 19 '19 at 14:50
  • 1
    @AlanBirtles TLDR: exception safety when creating multiple unique_ptr variables as function arguments and one of them throws. See e.g. https://stackoverflow.com/a/22571331/256138 – rubenvb Sep 19 '19 at 15:00

3 Answers3

6

std::move is named move for a reason. When you move from one std::unique_ptr to another, the one you move from becomes nullptr. It couldn't be any other way, it's a unique ptr after all, and two unique_ptr instances sharing the same data would violate that. (Also both going out of scope would call the deleter twice, and then all hell is loose.)

YSC
  • 38,212
  • 9
  • 96
  • 149
Max Vollmer
  • 8,412
  • 9
  • 28
  • 43
5

Why is u4 valid while u5 is not (nullptr) ?

Because u5 was moved from in the initialisation of u6, while u4 has not been moved from. The move constructor is guaranteed to set the moved from pointer to null. It cannot point to the same object as u6 since that would violate the uniqueness constraint.

Why is u7 valid but not u6 ?

Same reason.

eerorika
  • 232,697
  • 12
  • 197
  • 326
3

Moving a uniquely owned object means that the new owner has it and the previous owner has nothing left.
Much like how physical objects work in the real world.

Having nothing is indicated by the owner’s converting to a null pointer.

The object that was initially owned by u5 has been moved first into u6 and then into u7.

molbdnilo
  • 64,751
  • 3
  • 43
  • 82