Smart pointer confusion (std::unique_ptr in function argument)

Pages: 12
this is my first time working smart pointer and I getting confused with this:
1
2
3
4
5
6
7
8
9
10
11
12
void test(std::unique_ptr<int> s)
{
    cout << *s;//(s == nullptr);
}

int main()
{
    int * n = new int(55);
    test(std::unique_ptr<int>(n));
    test(std::make_unique<int>(n));
    return 0;
}


Wich of the the function call above is the correct one?
Last edited on
L9. unique_ptr takes as argument an already allocated pointer.

L10 with make_unique() takes a value as an argument (not a pointer) and allocates the required memory and returns a type std::unique_ptr.

Your compiler should squark at L10.

You can do this:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <memory>
#include <iostream>

void test(std::unique_ptr<int> s) {
	std::cout << *s;//(s == nullptr);
}

int main() {
	//int* n = new int(55);
	//test(std::unique_ptr<int>(n));
	test(std::make_unique<int>(55));
	return 0;
}


So I should choose L11?
ps: what does "squark" mean in this context?
Last edited on
The code that I have it has n declared like that:
int* n = new int(55); // with some other variable type but declared in this manner
So I should choose one the two options mentioned in the OP.
Sorry - it means make a lot of noise. ie the compiler displays errors. C++compilers are known for displaying multiple errors for one source code issue.

I believe @seeplus was implying that you were calling make_unique<int> with an argument of type int*. The int* pointer value will probably convert to an int value, but the compiler will probably issue a warning.

The unique_ptr you created will be pointing to an int in memory containing the value of n (a pointer value or address) rather than the value pointed to by n.

Try using make_unique(*n) in your original code, and you should be fine.
Core Guidelines:
R.30: Take smart pointers as parameters only to explicitly express lifetime semantics
F.7: For general use, take T* or T& arguments rather than smart pointers
R.32: Take a unique_ptr<widget> parameter to express that a function assumes ownership of a widget
R.33: Take a unique_ptr<widget>& parameter to express that a function reseats the widget

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rr-uniqueptrparam
Why use new at all? Why not just use smart pointers? If you use new, you need to also use delete where appropriate to free the allocated memory. If you use smart pointers, then this is all taken care of.

The issue with using new/delete is who 'owns' the pointer and is responsible for it's deletion. If all a function requires is access to the already allocated memory, then there is no need to pass via a smart pointer. Just pass as a normal pointer. If your function takes a unique_ptr as a param, then you're passing the 'ownership' of the memory from the caller to the function. Note that you can't copy unique_ptr. You can only use move semantics with it.

Consider:

1
2
3
4
5
6
7
8
9
10
#include <memory>
#include <iostream>

void test(const int* p) {
	std::cout << *p << '\n';
}

int main() {
	test(std::make_unique<int>(55).get());
}

Why use new at all?


Because I migrating some code where the function prototypes have unique_prt<T> in as parameter.

@JLBorges
R.32 is my case and the widget I need to pass is created using the new operator,
So the question is how would I call the function.
Other thing to note is passing a std::unique_ptr<> by value into a function. A std::unique_ptr<> can't be copied, it can only be moved. So passing one by value actually moves the smart pointer.

That transfers the ownership of the contained pointer to the temporary function. When the function returns it either has to return ownership back to the caller or the unique_ptr in the caller now holds a null value.

The unique_ptr in the function is a temporary that is destroyed when the function terminates.

Using and passing a std::shared_ptr<> would simply add a reference count and "create a temporary copy for the function", and when the function returns that temp copy is destroyed and the reference count is reduced by one.

What actually happens when passing a shared_ptr is up to the implementation, creating a temp copy doesn't necessarily have to happen. The actual shared smart pointer could be passed via reference/pointer and we only semantically think a copy is made since that is what happens when passing by value a non-smart pointer object.

Passing a smart pointer in and out of functions can be different vs. using a raw pointer.
https://www.internalpointers.com/post/move-smart-pointers-and-out-functions-modern-c

Unless you actually need to manipulate the smart pointer itself directly in the function pass the smart pointer's underlying object instead.

As "clunky" as the std::make_unique<>() syntax appears to be, it explicitly declares what is happening without the need for using new. Using new happens "behind the scenes" within the function.
1
2
3
4
5
6
7
8
void test(std::unique_ptr<widget> s) ; // function assumes ownership of the object ;

int main()
{
    widget* w = new widget(....) ; // legacy code: widget is created using a naked new
    // ...
    test( std::unique_ptr<widget>(w) ); // hand over ownership of the object to the function
}


After clean up, it could become:
1
2
3
4
5
6
7
8
void test(std::unique_ptr<widget> s) ; // function assumes ownership of the object ;

int main()
{
    auto w = std::make_unique<widget>(....) ; 
    // use w
    test( std::move(w) ); // hand over ownership of the object to the function
}


Or if we do not need to use the widget before calling the function:
1
2
3
4
5
6
void test(std::unique_ptr<widget> s) ; // function assumes ownership of the object ;

int main()
{
    test( std::make_unique<widget>(....) ); // hand over ownership of the object to the function
}
Last edited on
@George P Thank you for the explanation, it was helpful.

@JLBorges I think your examples did it for me.
In most of the code that I need to migrate the widget is used before calling the function so I think the first example is the right one for my case because the code is expressed in that manner. Thank you.
the widget is used before calling the function


Needing to use the widget before calling the function doesn't prevent widget being initialised from a std::make_unique. The line containing the new would need to be changed and also the call the function. The line(s) using the value referenced by the smart pointer don't need to be changed. Consider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <memory>

void test(std::unique_ptr<int> s) {
	std::cout << *s << '\n';
}

int main() {
	auto w { std::make_unique<int>(66) };

	// ....
	std::cout << *w << '\n';

	test(std::move(w));
}


Note that if you new and then a unique_ptr with the returned pointer, make sure that the code somewhere doesn't also use delete on the same pointer. The memory will automatically be deleted. ie DON'T do:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <memory>

void test(std::unique_ptr<int> s) {
	std::cout << *s << '\n';
}

int main() {
	const auto w { new int(66) };

	// ....

	test(std::unique_ptr<int>(w));

	delete w;    // NO!!
}




Last edited on
@seeplus Yeah I am checking if there is some delete call on those pointers.
Thank you for all the explanation, it was pretty helpful to get a better understanding of smart pointers.
the same consideration would have to be taken into account when dealing shared_ptr also?
The main reason to use std::make_unique<> or std::make_shared<> when creating a smart pointer from a raw pointer is to create a new pointer/pointed-to object so the object can survive a raw pointer being deleted.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <memory>
int main()
{
   int* r_ptr { new int(55) };

   std::cout << &r_ptr << ", " << r_ptr << ", " << *r_ptr << '\n';

   std::unique_ptr<int> s_ptr { std::make_unique<int>(*r_ptr) };

   std::cout << &s_ptr << ", " << s_ptr.get()  << ", " << *s_ptr << '\n';

   delete r_ptr;

   std::cout << &s_ptr << ", " << s_ptr.get() << ", " << *s_ptr << '\n';
}
hbcpp wrote:
the same consideration would have to be taken into account when dealing shared_ptr also?
for that one if the function assumes ownership, it should pass by rvalue-ref instead of by value: https://github.com/isocpp/CppCoreGuidelines/issues/1916
The main reason to use std::make_unique<> or std::make_shared<> when creating a smart pointer from a raw pointer is to create a new pointer/pointed-to object so the object can survive a raw pointer being deleted.


But the smart pointer isn't being created from the raw pointer. It's being created from the data pointed to by the raw pointer.

You can create a smart pointer from a raw pointer at any time the raw pointer is valid.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <memory>

int main() {
	auto* r_ptr { new int(55) };

	std::cout << &r_ptr << ", " << r_ptr << ", " << *r_ptr << '\n';

	std::unique_ptr<int> s_ptr {r_ptr};

	std::cout << &s_ptr << ", " << s_ptr.get() << ", " << *s_ptr << '\n';

	// delete r_ptr;	// Now not required
}

unique_ptr has one and only one owner. That owner will delete the allocated memory when unique_ptr goes out of scope. unique_ptr can't be copied. It is move only.

shared_ptr is where there is no one defined owner and can be copied. shared_ptr maintains a count of 'usage'. Every copy increments usage count and every time a shared_ptr goes out of scope, usage is decremented. When usage is decremented to 0, there are no longer any 'owners' and the allocated memory is then and only then deleted.

You need to decide whether you need a shared_ptr or whether unique_ptr will suffice and just pass a raw pointer to those functions that need access to the data.

Because of the need to maintain a usage count, there is a slight overhead in using shared_ptr as opposed to unique_ptr.

NB. In some old C++ books and old on-line resources, you may come across reference to std::auto_ptr. This was a first attempt of doing a smart pointer. It had some serious issues and was replaced with unique_ptr/shared_ptr in C++11. auto_ptr was removed in C++17. If you do come across it, get a more up-to-date resource!
> unique_ptr has one and only one owner. That owner will delete the allocated memory

The defaults for these smart pointers use the default deleter std::default_delete which uses delete (or delete[]) to destroy the object(s) and deallocate memory.

Standard smart pointers are general purpose resource management wrappers; we can provide a custom deleter for custom resources. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <memory>
#include <cstdio>

int main ()
{
    const auto noisy_fclose = []( std::FILE* cfile )
    {
        std::cout << "\n*** deleter: closing the C file stream ***\n";
        std::fclose(cfile) ;
    };

    std::unique_ptr< std::FILE, decltype(noisy_fclose) > file( std::fopen( __FILE__, "r" ) ) ;
                                      // prior to C++20: file( std::fopen( __FILE__, "r" ), noisy_fclose ) ;
    int c ;
    while( ( c = std::fgetc( file.get() ) ) != EOF ) std::putchar(c) ;
}

http://coliru.stacked-crooked.com/a/87fb9e2517cdf478
Pages: 12