Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initializing a std::unique_ptr by passing the address of the pointer

I am creating a class which interops with some Windows API code, now one of the pointers I have to initialize is done by calling a native function which initializes it.

My pointers are of type std::unique_ptr with a custom deleter, which calls the WinAPI deleter function provided, however I cannot pass the unique_ptr with the & address-of operator to the init-function. Why?

I have created a sample that demonstrates my problem:

#include <memory>  struct foo {    int x; };  struct custom_deleter {};  void init_foo(foo** init) {   *init = new foo(); }  int main() {    std::unique_ptr<foo, custom_deleter> foo_ptr;     init_foo(&foo_ptr); } 

The compiler barks and says:

source.cpp: In function 'int main()': source.cpp:19:21: error: cannot convert 'std::unique_ptr<foo, custom_deleter>*' to 'foo**' for argument '1' to 'void init_foo(foo**)' 
like image 649
Tony The Lion Avatar asked Sep 13 '12 09:09

Tony The Lion


People also ask

What is std :: unique_ptr?

std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope. The object is disposed of, using the associated deleter when either of the following happens: the managing unique_ptr object is destroyed.

Can unique_ptr be copied?

A unique_ptr does not share its pointer. It cannot be copied to another unique_ptr , passed by value to a function, or used in any C++ Standard Library algorithm that requires copies to be made. A unique_ptr can only be moved.


2 Answers

Somewhere under the covers, unique_ptr<foo> has a data member of type foo*.

However, it's not legitimate for a user of the class to directly modify that data member. Doing so would not necessarily preserve the class invariants of unique_ptr, in particular it wouldn't free the old pointer value (if any). In your special case you don't need that to happen, because the previous value is 0, but in general it should happen.

For that reason unique_ptr doesn't provide access to the data member, only to a copy of its value (via get() and operator->). You can't get a foo** out of your unique_ptr.

You could instead write:

foo *tmp; init_foo(&tmp); std::unique_ptr<foo, custom_deleter> foo_ptr(tmp); 

This is exception-safe for the same reason that std::unique_ptr<foo, custom_deleter> foo_ptr(new foo()); is exception-safe: unique_ptr guarantees that whatever you pass in to its constructor will eventually get deleted using the deleter.

Btw, doesn't custom_deleter need an operator()(foo*)? Or have I missed something?

like image 54
Steve Jessop Avatar answered Sep 22 '22 09:09

Steve Jessop


Steve has already explained what the technical problem is, however, the underlying problem goes much deeper: The code employs an idiom helpful when you deal with naked pointers. Why does this code do two-step initialization (first create the object, then initialize it) in the first place? Since you want to use smart pointers, I'd suggest you carefully adapt the code:

foo* init_foo() {   return new foo(); }  int main() {    std::unique_ptr<foo, custom_deleter> foo_ptr( init_foo() );  } 

Of course, renaming init_foo() to create_foo() and having it return a std::unique_ptr<foo> directly would be better. Also, when you use two-step initialization, it's often advisable to consider using a class to wrap the data.

like image 35
sbi Avatar answered Sep 18 '22 09:09

sbi