Kyrylo Kovalenko
Kyrylo Kovalenko

Reputation: 2171

Using unique_ptr / shared_ptr with API functions returning resources as out parameters via pointer

I’m catching up now with C++ 11/14 stuff in my current project. I have trouble using unique_ptr/shared_ptr with API functions returning resources as out parameters via pointer.

Let’s consider DsGetDcName(..., __out PDOMAIN_CONTROLLER_INFO* ppDCI) as an example.

Before c++11 there was a widespread pattern to work with Windows APIs. I would have a RIIA class that holds PDOMAIN_CONTROLLER_INFO, calls NetApiBufferFree in destructor, and has PDOMAIN_CONTROLLER_INFO* operator&() so that I can write:

info_ptr spInfo;
DsGetDcName(..., &spInfo);

Now with C++ 11/14 I see a lot of articles advocating for unique_ptr/shared_ptr with a custom deleter to release a resource in a proper way. And it really works well when a resource is returned as an r-value from a function (LoadLibrary is a good example of this).

One solution I found is to attach a raw-pointer to a smart pointer after the API call:

PDOMAIN_CONTROLLER_INFO pDCI = nullptr;
DsGetDcName(..., &pDCI);
std::unique_ptr<DOMAIN_CONTROLLER_INFO, decltype(&NetApiBufferFree)> spInfo(pDCI, &NetApiBufferFree);

However, this is not the way I like. As far as I understand, new smart pointers were not designed to work for out parameters probably because of the ‘safty first’ approach.

Can something be really done here? Perhaps some kind of functor class serving as out parameter proxy so I can write DsGetDcName(..., &out_param(spInfo)) ?

Upvotes: 3

Views: 617

Answers (2)

Kyrylo Kovalenko
Kyrylo Kovalenko

Reputation: 2171

So far I came up with this code:

template<typename T>
class out
{
    T* _p;
    std::shared_ptr<T>& _sp;
public:
    out(std::shared_ptr<T>& sp) : _p(nullptr) , _sp(sp) {}
    ~out() { _sp.reset(_p); }
    T** operator&() { _ASSERT(nullptr == _p); return &_p; }
};

But it has 2 disadvantages

  1. When used it looks heavy because class template type is not deduced from the constructor, e.g. I have to write GetMeow(&out<Meow>(sp)) instead of GetMeow(&out(sp))
  2. The same name does not work for simultaneously for shared_ptr and unique_ptr.

Are there any template ninjas?

EDIT:

Ok, I've combined Rudolfs' work with some of my tweaks and the final result is here: https://gist.github.com/kirillkovalenko/8219e7c1afea9b5da5da

Upvotes: 0

Rudolfs Bundulis
Rudolfs Bundulis

Reputation: 11944

Well, you can make a temporary wrapper that will initialize your std::unique_ptr upon destruction:

#include <windows.h>
#include <Dsgetdc.h>
#include <Lm.h>

#include <memory>

template<typename T, typename D>
struct OutParameterWrapper
{
    OutParameterWrapper(std::unique_ptr<T, D>& Target) : m_Target(Target), m_pSource(nullptr)
    {
    }
    ~OutParameterWrapper()
    {
        m_Target.reset(m_pSource);
    }
    operator T**()
    {
        return &m_pSource;
    }
    std::unique_ptr<T, D>& m_Target;
    T* m_pSource;
};

int main(int argc, char** argv)
{
    std::unique_ptr<DOMAIN_CONTROLLER_INFO, decltype(&NetApiBufferFree)> spInfo(nullptr, NetApiBufferFree);
    DsGetDcName(NULL, NULL, NULL, NULL, 0, OutParameterWrapper<DOMAIN_CONTROLLER_INFO, decltype(&NetApiBufferFree)>(spInfo));
    return 0;
}

EDIT

To have automatic deduction and have it work for both std::unique_ptr and std::shared_ptr you can do something like this:

#include <windows.h>
#include <Dsgetdc.h>
#include <Lm.h>

#pragma comment(lib, "Netapi32.lib")

#include <memory>

template<typename T, typename P>
struct OutParameterWrapper
{
    OutParameterWrapper(P& Target) : m_Target(Target), m_pSource(nullptr)
    {
    }
    ~OutParameterWrapper()
    {
        m_Target.reset(m_pSource);
    }
    operator T**()
    {
        return &m_pSource;
    }

    P& m_Target;
    T* m_pSource;
};

template<typename P>
OutParameterWrapper<typename P::element_type, P> MakeOutParameterWrapper(P& Target)
{
    return OutParameterWrapper<typename P::element_type,P>(Target);
}

int main(int argc, char** argv)
{
    std::unique_ptr<DOMAIN_CONTROLLER_INFO, decltype(&NetApiBufferFree)> spInfo(nullptr, NetApiBufferFree);
    std::shared_ptr<DOMAIN_CONTROLLER_INFO> spInfo2(nullptr, NetApiBufferFree);
    auto nResult = DsGetDcName(NULL, NULL, NULL, NULL, 0, MakeOutParameterWrapper(spInfo));
    DsGetDcName(NULL, NULL, NULL, NULL, 0, MakeOutParameterWrapper(spInfo2));
    return 0;
}

Upvotes: 6

Related Questions