Инициализация std :: unique_ptr путем передачи адреса указателя

Я создаю класс, который взаимодействует с некоторым кодом Windows API, теперь один из указателей, которые мне нужно инициализировать, выполняется путем вызова встроенной функции, которая его инициализирует.

Мои указатели имеют тип std::unique_ptr с пользовательским средством удаления, которое вызывает функцию удаления WinAPI, однако я не могу передать 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**)'

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*)? Или я что-то пропустил?

19

Другие решения

Стив уже объяснил, в чем техническая проблема, однако основная проблема гораздо глубже: в коде используется идиома, полезная, когда вы имеете дело с голыми указателями. Почему этот код выполняет двухэтапную инициализацию (сначала создайте объект, затем инициализируйте его) в первую очередь? Поскольку вы хотите использовать умные указатели, я бы посоветовал вам тщательно адаптировать код:

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> напрямую было бы лучше. Кроме того, когда вы используете двухэтапную инициализацию, часто рекомендуется рассмотреть возможность использования класса для переноса данных.

5

Вы можете использовать следующий трюк:

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));
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};
}
0
По вопросам рекламы [email protected]