32

我正在创建一个与一些 Windows API 代码互操作的类,现在我必须初始化的指针之一是通过调用初始化它的本机函数来完成的。

我的指针是std::unique_ptr带有自定义删除器的类型,它调用提供的 WinAPI 删除器函数,但是我无法将带有 & address-of 运算符的 unique_ptr 传递给 init 函数。为什么?

我创建了一个示例来演示我的问题:

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

编译器咆哮着说:

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**)'
4

4 回答 4

25

在某个地方,unique_ptr<foo>有一个类型为 的数据成员foo*

但是,该类的用户直接修改该数据成员是不合法的。这样做不一定会保留 的类不变量unique_ptr,特别是它不会释放旧的指针值(如果有的话)。在您的特殊情况下,您不需要发生这种情况,因为先前的值为 0,但通常它应该发生。

出于这个原因unique_ptr,不提供对数据成员的访问,只提供对其值的副本(通过get()operator->)。你无法foo**从你的unique_ptr.

你可以改为写:

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

出于与异常安全相同的原因,这std::unique_ptr<foo, custom_deleter> foo_ptr(new foo());是异常安全的:unique_ptr保证您传递给其构造函数的任何内容最终都将使用删除器删除。

顺便说一句,custom_deleter不需要operator()(foo*)吗?还是我错过了什么?

于 2012-09-13T10:13:18.510 回答
7

Steve 已经解释了技术问题是什么,然而,潜在的问题要深得多:代码使用了一个习惯用法,当您处理裸指针时很有帮助。为什么这段代码首先要做两步初始化(首先创建对象,然后初始化它)?由于您想使用智能指针,我建议您仔细调整代码:

foo* init_foo()
{
  return new foo();
}

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

}

当然,重命名init_foo()create_foo()让它直接返回 astd::unique_ptr<foo>会更好。此外,当您使用两步初始化时,通常建议考虑使用类来包装数据。

于 2012-09-13T10:46:51.433 回答
0

您可以使用以下技巧:

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)

进而:

std::unique_ptr<foo, custom_deleter> foo_ptr;
init_foo(&ptr_setter(foo_ptr));
于 2016-09-10T10:59:00.607 回答
0

我最终想出了一种方法,允许使用如下代码初始化 unique_ptr:

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

这是解决方案:

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};
}
于 2018-10-14T17:09:01.643 回答