33

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**)'
Tony The Lion
  • 61,704
  • 67
  • 242
  • 415

4 Answers4

26

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?

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • @ForEverR: what about it? It returns the *value* of that data member, not a pointer to that data member. – Steve Jessop Sep 13 '12 at 10:18
  • 3
    @Tony: `shared_ptr` is the same as `unique_ptr`. If you need to use an interface like `init_foo` that deals in raw pointers, then yes, you need to use raw pointers. Then you put the raw pointer into a suitable smart pointer as soon as possible. – Steve Jessop Sep 13 '12 at 10:19
  • @SteveJessop Thanks :) Makes sense. Now, you're right, it does need the `operator()(foo*)`. My example is incomplete in that respect – Tony The Lion Sep 13 '12 at 10:22
7

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.

sbi
  • 219,715
  • 46
  • 258
  • 445
  • seeing that my `init_foo` is a windows API call, I cannot change it. So do you suggest I write a wrapper around that function? Why though? Exception safety is not a problem per @SteveJessop. – Tony The Lion Sep 13 '12 at 11:04
  • 2
    @Tony: sbi's code is just "cleaner", the single line in `main` does what it says. My code needs a temp variable in order to create the thing you actually want. That's not preferable, my code is just what you have to do to call `init_foo` as it was. – Steve Jessop Sep 13 '12 at 13:06
  • 1
    @SteveJessop: "...and having it return a `std::unique_ptr` directly would be better." – sbi Sep 13 '12 at 13:27
  • 1
    @sbi: yeah, that! Although the deleter type is part of the `unique_ptr` type, and I don't think the questioner *really* allocates with `new`. – Steve Jessop Sep 13 '12 at 13:28
  • @SteveJessop: I took the `new` from the OP and left out the deleter as a concession to my laziness. `:-/` – sbi Sep 13 '12 at 13:53
0

You can use the following trick:

template<class T>
class ptr_setter
{
public:
    ptr_setter(T& Ptr): m_Ptr{Ptr} {}
    ~ptr_setter() { m_Ptr.reset(m_RawPtr); }

    ptr_setter(const ptr_setter&) = delete;
    ptr_setter& operator=(const ptr_setter&) = delete;

    auto operator&() { return &m_RawPtr; }

private:
    T& m_Ptr;
    typename T::pointer m_RawPtr{};
};


// Macro will not be needed with C++17 class template deduction.
// If you dislike macros (as all normal people should)
// it's possible to replace it with a helper function,
// although this would make the code a little more complex.

#define ptr_setter(ptr) ptr_setter<decltype(ptr)>(ptr)

and then:

std::unique_ptr<foo, custom_deleter> foo_ptr;
init_foo(&ptr_setter(foo_ptr));
0

I eventually came up with an approach that allows to initialise unique_ptr's with a code like this:

struct TOpenSSLDeleter { ... }; // Your custom deleter
std::unique_ptr<EVP_MD_CTX, TOpenSSLDeleter> Ctx;
...
Ctx = MakeUnique(EVP_MD_CTX_create()); // MakeUnique() accepts raw pointer

And here is the solution:

template <class X>
struct TUniquePtrInitHelper {
   TUniquePtrInitHelper(X *Raw) noexcept {
      m_Raw = Raw;
   }
   template <class T, class D>
   operator std::unique_ptr<T, D>() const noexcept {
      return std::unique_ptr<T, D>(m_Raw);
   }

private:
   X *m_Raw;
};
template <class X>
TUniquePtrInitHelper<X> MakeUnique(X *Raw) noexcept {
   return {Raw};
}
DimanNe
  • 1,791
  • 3
  • 12
  • 19