我正在创建一个与一些Windows API代码交互的类,现在我必须通过调用一个初始化它的本机函数来完成初始化之一。
我的指针是std::unique_ptr
类型,带有自定义删除器,它调用所提供的WinAPI删除函数,但是我无法通过&传递unique_ptr。 init-function的address-of运算符。为什么呢?
我创建了一个演示我问题的示例:
#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**)'
答案 0 :(得分:20)
在某个地方,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*)
?或者我错过了什么?
答案 1 :(得分:6)
史蒂夫已经解释了技术问题是什么,然而,潜在的问题更加深入:当你处理裸指针时,代码使用了一个有用的习惯用法。为什么这段代码首先进行两步初始化(首先创建对象,然后初始化它)?既然你想使用智能指针,我建议你仔细调整代码:
foo* init_foo()
{
return new foo();
}
int main()
{
std::unique_ptr<foo, custom_deleter> foo_ptr( init_foo() );
}
当然,将init_foo()
重命名为create_foo()
并让其直接返回std::unique_ptr<foo>
会更好。此外,当您使用两步初始化时,通常建议考虑使用类来包装数据。
答案 2 :(得分: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));
答案 3 :(得分: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};
}