Reputation: 2171
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
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
GetMeow(&out<Meow>(sp))
instead of GetMeow(&out(sp))
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
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