Почему статическое снижение рейтинга unique_ptr небезопасно? - PullRequest
6 голосов
/ 10 октября 2019

Я имею в виду следующий вопрос к «Даункастинг» unique_ptr к unique_ptr , который, как мне кажется, сам по себе имеет смысл.

OP запрашиваетполучение unique_ptr<Derived> из unique_ptr<Base>, где последний объект имеет динамический тип Derived, так что статический спад будет безопасным.

Обычно, (как 95% решений в Интернетепредположить, грубо говоря), простое решение было бы:

unique_ptr<Derived> ptr(static_cast<Derived*>(baseClassUniquePtr.release()));

OP также утверждает, хотя

PS. Есть дополнительное осложнение в том, что некоторые фабрики находятся в DLL, которые динамически загружаются во время выполнения, что означает, что мне нужно убедиться, что созданные объекты уничтожаются в том же контексте (пространстве кучи), в котором они были созданы. Передача права собственности (что обычно происходит в другом контексте) должна затем обеспечить удаление из исходного контекста. Но помимо необходимости предоставлять / приводить средство удаления вместе с указателем, проблема приведения должна быть такой же.

Теперь, похоже, решение состоит в том, чтобы получить средство удаления из объекта unique_ptr<Base> ипередать его новому объекту, что явно приводит к unique_ptr<Derived, default_delete<Base>>. Но default_delete в любом случае не имеет статуса. Единственная разница - аргумент шаблона. Но так как мы всегда объявляем dtor virtual при использовании наследования с динамическим полиморфизмом в C ++, мы всегда вызываем ~Derived в любом случае, так что я думаю, что исходное средство удаления в любом случае будет безопасным, позволяя чистое приведение к unique_ptr<Derived>(без необязательного второго аргумента шаблона, который запрещает любое обычное хранение).

Итак, я понимаю, что у нас есть два пространства кучи при использовании библиотеки (DLL, .dylib, ...), которая создает объект и передаетэто к некоторому исполняемому файлу, я не понимаю, как копирование / перемещение средства удаления без сохранения состояния из старого объекта решает эту проблему.

Может ли решить эту проблему? Если да, то как? Если нет, как мы можем решить эту проблему?

--- Редактировать: Также ... get_deleter возвращает ссылку на объект, лежащий в старом unique_ptr, который разрушается, когда этот старый unique_ptrуничтожен, не так ли? --- (глупый вопрос, потому что unique_ptr вместе с удалителем явно перемещен)

1 Ответ

1 голос
/ 10 октября 2019

Может ли решить проблему?

Вы правы, это не так (само по себе). Но это не потому, что средство удаления по умолчанию не имеет состояния, а потому, что его реализация встроена.

Если нет, как мы можем решить эту проблему?

Мы должныубедитесь, что вызов delete происходит из модуля, из которого объект был первоначально выделен (назовем его модулем A). Поскольку std::default_delete является шаблоном, он создается по требованию, и встроенная версия вызывается из модуля B. Нехорошо.


Метод 1

Одним из решений является использованиекастомное удаление полностью. Он не должен быть состоящим из состояния, если его реализация находится в модуле A.

// ModuleA/ModuleADeleter.h

template <class T>
struct ModuleADeleter {
    // Not defined here to prevent accidental implicit instantiation from the outside
    void operator()(T const *object) const;
};

// Suppose BUILDING_MODULE_A is defined when compiling module A
#ifdef BUILDING_MODULE_A
    #define MODULE_A_EXPORT __declspec(dllexport)
#else
    #define MODULE_A_EXPORT __declspec(dllimport)
#endif

template class MODULE_A_EXPORT ModuleADeleter<Base>;
template class MODULE_A_EXPORT ModuleADeleter<Derived>;

// ModuleA/ModuleADeleter.cpp

#include "ModuleA/ModuleADeleter.h"

template <class T>
void ModuleADeleter<T>::operator()(T const *object) const {
    delete object;
}

template class ModuleADeleter<Base>;
template class ModuleADeleter<Derived>;

(описан импорт / экспорт шаблонов из DLL там ).

На данный момент нам просто нужно вернуть std::unique_ptr<Base, ModuleADeleter<Base>> из модуля A и последовательно преобразовать в std::unique_ptr<Derived, ModuleADeleter<Derived>> при необходимости.

Обратите внимание, что ModuleADeleter<Derived>нужен только если Base имеет не виртуальный деструктор, в противном случае простое повторное использование ModuleADeleter<Base> (как и связанный ответ) будет работать как задумано.


Метод 2

Самый простойРешением является использование std::shared_ptr вместо std::unique_ptr. Это немного снижает производительность, но вам не нужно внедрять и обновлять программу удаления или конвертировать ее вручную. Это работает, потому что std::shared_ptr создает и удаляет тип своего удалителя при построении, которое выполняется внутри модуля A. Затем этот удалитель сохраняется и сохраняется до тех пор, пока он не потребуется, и не появляется в типе указателя, так что вы можете смешивать указатели на объектысвободно создается из различных модулей.


Также ... get_deleter возвращает ссылку на объект, лежащий в старом unique_ptr, который уничтожается при уничтожении этого старого unique_ptr,не так ли?

Нет, возвращаемое значение get_deleter относится к средству удаления, содержащемуся в unique_ptr, для которого вы его вызвали. Способ передачи состояния средства удаления при перемещении между unique_ptr s описан в перегрузке # 6 здесь .

...