1

Using pointers in the example below gave me a confusion so I probably misunderstand something. I will try to show my way of understanding and some pseudo-simple example.

Creating std::unique_ptr variable of name my_int ensures that it can not be copied and there is only one owner of it's int object. Then I create a vector to storage the pointer and put it without copying with std::emplace_back(). Now I would like to check if the element of vector integers[0] has the same address in memory as the orginal element my_int. I thought it should because this element could not be copied.

Example code:

auto my_int = std::make_unique<int>(1);
std::vector<std::unique_ptr<int>> integers;
integers.emplace_back(my_int.get());

std::cout<< "Value of my_int: " << *my_int << std::endl;
std::cout << "Address of my_int: " << my_int.get() << std::endl;
std::cout << "Also address of my_int: " << &my_int << std::endl;
std::cout << "Also address of my_int: " << &integers[0]<< std::endl;
std::cout << "Also address of my_int: " << integers[0].get()<< std::endl;

Result of test:

Value of my_int: 1
Address of my_int: 0x260fc40
Also address of my_int: 0x65fb58
Also address of my_int: 0x260fe20
Also address of my_int: 0x260fc40

I also tried using std::move() when putting the object to vector but the effect is the same, addresses differs but I don't understand why.

My first confusion is that &my_int and my_int.get() is not the same and the second confusion is that also integers[0].get() differs from &integers[0].

root
  • 192
  • 13
Kuba
  • 11
  • 2

2 Answers2

6

The expression my_int.get() returns a pointer to the contained integer, its type is int*.

The expression &my_int returns a pointer to the my_int object itself, and has the type std::unique_ptr<int>*.

And integers[0] is a distinct and different object from my_int. They just happen to contain the same pointer. This is bad for a unique pointer: together, they will try to free the same memory twice. You must use std::move

auto my_int = std::make_unique<int>(1);
std::vector<std::unique_ptr<int>> integers;
integers.emplace_back(std::move(my_int)); // push_back also works

This way my_int will be set to contain nullptr, and only integers[0] will contain the pointer to the allocated memory.


To understand my_int (or pointers in general really) better you might think of it something like this:

+--------+     +---+
| my_int | --> | 1 |
+--------+     +---+
^              ^
|              |
&my_int        my_int.get()

It's the same thing with integers[0].get() and &integers[0].

HTNW
  • 27,182
  • 1
  • 32
  • 60
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • Thank you for your explanation. Now I understand difference. I tested also `(&my_int)->get()` instead of raw `&my_int` and it's address now is the same as `my_int.get()`. It's looks sophisticated but after your explanation and rewriting it's easier for me to understand. Also it's nice(?) that _unique_ pointer can be hacked so it's not unique anymore. – Kuba Jan 05 '21 at 16:12
  • Having "my_int" in the left box is a bit confusing to me. I would put "address of int" or something along those lines. – eerorika Jan 05 '21 at 16:16
1

Creating std::unique_ptr variable of name my_int ensures that it can not be copied and there is only one owner of it's int object.

It is only the std::unique_ptr that cannot be copied. But unique pointer cannot guarantee that you won't copy the bare pointer that it owns. Copying the bare pointer owned by the unique pointer is what exactly you did which results in two "unique" owners of the same resource. And that will result in undefined behaviour when the second unique pointer is destroyed.

When you call std::unique_ptr::get, the unique pointer won't release its ownership to you. You should never pass a pointer, that isn't owned by you, into constructor of a smart pointer.

To fix this, you can transfer the ownership from one smart pointer into another by move:

integers.emplace_back(std::move(my_int));

addresses differs but I don't understand why.

The unique pointer variable is one object, and the element of the vector is another object. Since they are separate objects, they have a separate address. Even if they happen to be pointers that point to the same object - the integer, which is yet another object. Those three objects correspond to the three different addresses that you see in the output.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Thank you for explaining `std::move` concept, transfering the ownership is assimilable. After using this function I can not access my_int.get() which now I think is correct behaviour (there is an expection). Is it true that smart pointers and pointers should not be mixed if it is possible in some parts of software? – Kuba Jan 05 '21 at 18:12
  • @Kuba No, you can mix pointers and smart pointers. What you should avoid having is raw pointers that own a resource. In other words, you should avoid writing `delete ptr;` or `std::free(ptr);` except for in a deleter of a smart pointer. Likewise, you should avoid passing bare pointers into smart pointer constructors except for in functions whose sole purpose is to do just that (`std::make_unique` is an example of a function that does such thing internally). – eerorika Jan 05 '21 at 18:21