【问题标题】:Using unique_ptr / shared_ptr with API functions returning resources as out parameters via pointer将 unique_ptr / shared_ptr 与 API 函数一起使用,通过指针将资源作为输出参数返回
【发布时间】:2015-10-17 13:40:25
【问题描述】:

我现在正在赶上我当前项目中的 C++ 11/14 内容。我在使用unique_ptr/shared_ptr 时遇到问题,API 函数通过指针将资源作为输出参数返回。

我们以DsGetDcName(..., __out PDOMAIN_CONTROLLER_INFO* ppDCI) 为例。

在 c++11 之前,有一种使用 Windows API 的广泛模式。我会有一个 RIIA 类,它包含 PDOMAIN_CONTROLLER_INFO,在析构函数中调用 NetApiBufferFree,并拥有 PDOMAIN_CONTROLLER_INFO* operator&(),以便我可以编写:

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

现在有了 C++ 11/14,我看到很多文章提倡 unique_ptr/shared_ptr 使用自定义删除器以适当的方式释放资源。当资源作为 r 值从函数返回时,它确实很有效(LoadLibrary 就是一个很好的例子)。

我发现的一个解决方案是在 API 调用之后将原始指针附加到智能指针:

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

但是,这不是我喜欢的方式。据我了解,新的智能指针并不是为输出参数而设计的,可能是因为“安全第一”的方法。

真的可以在这里做点什么吗?也许某种函子类作为输出参数代理,所以我可以写DsGetDcName(..., &amp;out_param(spInfo))?

【问题讨论】:

  • std::unique_ptr 的全部目的是将RAII 应用于指针。如果你已经有这样的设置,我认为你不应该改变。
  • 问题在于std::unique_ptr 没有提供覆盖的operator&amp; 来访问内部指针的地址,因此可以将其传递给API。

标签: pointers winapi c++11 smart-pointers c++14


【解决方案1】:

好吧,您可以制作一个临时包装器,在销毁时初始化您的 std::unique_ptr:

#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;
}

编辑

要进行自动扣除并使其适用于std::unique_ptr 和std::shared_ptr,您可以执行以下操作:

#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;
}

【讨论】:

  • 我今天也想出了一个类似的解决方案,它有效。尽管我使用了一个较短的形式(class out),但它看起来仍然过于庞大,更重要的是它对 unique_ptr 和 shared_ptr 都不起作用。我想需要更多的模板魔法。
  • @KirillKovalenko - 在你给出更详细的限制后我更新了答案:)
【解决方案2】:

到目前为止,我想出了这个代码:

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; }
};

但它有两个缺点

  1. 使用时看起来很重,因为类模板类型不是从构造函数推导出来的,例如我必须写 GetMeow(&amp;out&lt;Meow&gt;(sp)) 而不是 GetMeow(&amp;out(sp))
  2. 同一名称不能同时用于shared_ptr 和unique_ptr。

有模板忍者吗?

编辑:

好的,我将 Rudolfs 的工作与我的一些调整结合起来,最终结果在这里:https://gist.github.com/kirillkovalenko/8219e7c1afea9b5da5da

【讨论】:

  • 当你想对一个类进行模板推导时,使用外部函数。看看std::make_pair 的std::make_shared,这就是他们的目的。
  • 在你的情况下,我会使用类似template &lt;class T&gt; out&lt;T&gt; make_out(std::shared_ptr&lt;T&gt; ptr) {return (out&lt;T&gt;(ptr));} Just for formatting
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-24
  • 2010-12-23
  • 1970-01-01
  • 2021-02-05
  • 1970-01-01
相关资源
最近更新 更多