Я нашел код с использованием std :: shared_ptr для произвольной очистки при завершении работы. Сначала я подумал, что этот код не может работать, но потом попробовал следующее:
#include <memory>
#include <iostream>
#include <vector>
class test {
public:
test() {
std::cout << "Test created" << std::endl;
}
~test() {
std::cout << "Test destroyed" << std::endl;
}
};
int main() {
std::cout << "At begin of main.\ncreating std::vector<std::shared_ptr<void>>"
<< std::endl;
std::vector<std::shared_ptr<void>> v;
{
std::cout << "Creating test" << std::endl;
v.push_back( std::shared_ptr<test>( new test() ) );
std::cout << "Leaving scope" << std::endl;
}
std::cout << "Leaving main" << std::endl;
return 0;
}
Эта программа дает результат:
At begin of main.
creating std::vector<std::shared_ptr<void>>
Creating test
Test created
Leaving scope
Leaving main
Test destroyed
У меня есть несколько идей о том, почему это может сработать, которые связаны с внутренним устройством std :: shared_ptrs, реализованным для G ++. Поскольку эти объекты обертывают внутренний указатель вместе со счетчиком, приведение std::shared_ptr<test>
к std::shared_ptr<void>
которому, вероятно, не препятствует вызову деструктора. Верно ли это предположение?
И, конечно же, гораздо более важный вопрос: гарантированно ли это будет работать по стандарту, или могут быть внесены дальнейшие изменения во внутренности std :: shared_ptr, другие реализации действительно нарушают этот код?
источник
Ответы:
Хитрость в том, что
std::shared_ptr
выполняется стирание шрифта. В основном, когда создается новый объект,shared_ptr
он сохраняет внутреннююdeleter
функцию (которая может быть передана в качестве аргумента конструктору, но, если она отсутствует, по умолчанию вызываетсяdelete
). Когдаshared_ptr
объект уничтожается, он вызывает эту сохраненную функцию, и она вызываетdeleter
.Здесь можно увидеть простой набросок стирания типа, которое происходит упрощенно с помощью std :: function и позволяет избежать подсчета ссылок и других проблем:
Когда a
shared_ptr
копируется (или создается по умолчанию) из другого, удалитель передается, так что, когда вы создаете ashared_ptr<T>
из a,shared_ptr<U>
информация о том, какой деструктор вызывать, также передается вdeleter
.источник
my_shared
. Я бы исправил это, но пока не имею права редактировать.std::shared_ptr<void>
позволяет мне избегать объявления бесполезного класса-оболочки, чтобы я мог унаследовать его от определенного базового класса.my_unique_ptr
. Когда вmain
шаблонеdouble
создается экземпляр, выбирается правильный удалитель, но он не является частью типаmy_unique_ptr
и не может быть получен из объекта. Тип удалителя стирается из объекта, когда функция получаетmy_unique_ptr
(скажем, по rvalue-ссылке), эта функция не знает и не должна знать, что такое удалитель.shared_ptr<T>
логически [*] имеет (как минимум) два соответствующих элемента данных:Ваша функция удаления
shared_ptr<Test>
, учитывая способ, которым вы ее создали , является нормальной функциейTest
, которая преобразует указательTest*
и преобразуетdelete
его.Когда вы
shared_ptr<Test>
вставляете ваш в векторshared_ptr<void>
, они оба копируются, хотя первый конвертируется вvoid*
.Итак, когда элемент вектора уничтожается, беря с собой последнюю ссылку, он передает указатель на средство удаления, которое уничтожает его правильно.
На самом деле это немного сложнее, чем это, потому что
shared_ptr
может принимать функтор удаления, а не просто функцию, поэтому могут быть даже сохранены данные для каждого объекта, а не просто указатель на функцию. Но в этом случае таких дополнительных данных нет, достаточно просто сохранить указатель на экземпляр функции шаблона с параметром шаблона, который фиксирует тип, с помощью которого указатель должен быть удален.[*] логически в том смысле, что у него есть доступ к ним - они могут быть не членами самого shared_ptr, а вместо некоторого узла управления, на который он указывает.
источник
shared_ptr
напрямую с соответствующим типом или используетеmake_shared
. Но, все - таки это хорошая идея , так как тип указателя может изменяться от строительства до тех пор, пока хранится вshared_ptr
:base *p = new derived; shared_ptr<base> sp(p);
, насколькоshared_ptr
обеспокоен объектbase
неderived
, так что вам нужен виртуальный деструктор. Этот шаблон может быть общим, например, с заводскими шаблонами.Это работает, потому что использует стирание типа.
По сути, когда вы создаете объект
shared_ptr
, он передает один дополнительный аргумент (который вы можете предоставить, если хотите), который является функтором удаления.Этот функтор по умолчанию принимает в качестве аргумента указатель на тип, который вы используете в
shared_ptr
, таким образом,void
здесь он соответствующим образом приводит его к статическому типу, который вы использовалиtest
здесь, и вызывает деструктор для этого объекта.Любая достаточно продвинутая наука похожа на магию, не так ли?
источник
shared_ptr<T>(Y *p)
Кажется, что конструктор действительно вызывает,shared_ptr<T>(Y *p, D d)
гдеd
автоматически создается средство удаления объекта.Когда это происходит, тип объекта
Y
известен, поэтому средство удаления для этогоshared_ptr
объекта знает, какой деструктор вызвать, и эта информация не теряется, когда указатель сохраняется в вектореshared_ptr<void>
.Действительно, спецификации требуют, чтобы для принимающего
shared_ptr<T>
объекта, чтобы принятьshared_ptr<U>
объект, должно быть верно, что онU*
должен быть неявно преобразован в a,T*
и это, безусловно, так,T=void
потому что любой указатель может бытьvoid*
неявно преобразован в a . Ничего не сказано о недействительном удалителе, так что спецификации действительно требуют, чтобы он работал правильно.Технически IIRC
shared_ptr<T>
содержит указатель на скрытый объект, который содержит счетчик ссылок и указатель на фактический объект; сохраняя удалитель в этой скрытой структуре, можно заставить эту явно волшебную функцию работать, сохраняя при этом размерshared_ptr<T>
обычного указателя (однако разыменование указателя требует двойного косвенного обращенияисточник
Test*
неявно преобразуется вvoid*
, следовательноshared_ptr<Test>
, неявно преобразуется вshared_ptr<void>
из памяти. Это работает, потому чтоshared_ptr
предназначено для управления уничтожением во время выполнения, а не во время компиляции, они будут внутренне использовать наследование для вызова соответствующего деструктора, как это было во время выделения.источник
Я собираюсь ответить на этот вопрос (2 года спустя), используя очень упрощенную реализацию shared_ptr, понятную пользователю.
Сначала я перейду к нескольким побочным классам: shared_ptr_base, sp_counted_base, sp_counted_impl и checked_deleter, последний из которых является шаблоном.
Теперь я собираюсь создать две «бесплатные» функции с именем make_sp_counted_impl, которые будут возвращать указатель на вновь созданную.
Хорошо, эти две функции важны для того, что будет дальше, когда вы создадите shared_ptr через шаблонную функцию.
Обратите внимание, что происходит выше, если T недействителен, а U - ваш "тестовый" класс. Он вызовет make_sp_counted_impl () с указателем на U, а не с указателем на T. Все управление уничтожением осуществляется здесь. Класс shared_ptr_base управляет подсчетом ссылок в отношении копирования и присваивания и т. Д. Класс shared_ptr сам управляет безопасным использованием перегрузок операторов (->, * и т. Д.).
Таким образом, хотя у вас есть shared_ptr для void, под ним вы управляете указателем того типа, который вы передали в new. Обратите внимание, что если вы конвертируете свой указатель в void * перед тем, как поместить его в shared_ptr, он не сможет скомпилировать в checked_delete, так что вы и там в безопасности.
источник