shared_ptr<int> r = make_shared<int>();
Нет никакой гарантии, что это вызовет new int
(что в любом случае не является строго наблюдаемым пользователем) или, в более общем смысле, new T
(что наблюдается для определенного пользователем, специфичного для класса operator new
);на практике это не произойдет (нет гарантии, что это не произойдет).
Последующее обсуждение касается не только shared_ptr
, но и «умных указателей» с владением семантика.Для любого интеллектуального указателя smart_owning :
Основная мотивация make_owning вместо smart_owning<T>(new T)
состоит в том, чтобы в любой момент избежать выделения памяти без владельца;это было важно в C ++, когда порядок вычисления выражений не давал гарантии, что оценка подвыражений в списке аргументов была непосредственно перед вызовом этой функции;исторически в C ++:
f (smart_owning<T>(new T), smart_owning<U>(new U));
можно оценить как:
T *temp1 = new T;
U *temp2 = new U;
auto &&temp3 = smart_owning<T>(temp1);
auto &&temp4 = smart_owning<U>(temp2);
Таким образом, temp1
и temp2
не управляются никаким объектом-владельцем в течение нетривиального времени:
- очевидно,
new U
может выдать исключение - создание интеллектуального указателя-владельца обычно требует выделения (небольших) ресурсов и может выдать
Так что либоtemp1
или temp2
могут быть пропущены (но не оба), если возникнет исключение, что и было именно той проблемой, которую мы пытались избежать в первую очередь.Это означает, что составные выражения, включающие конструирование умных указателей, были плохой идеей;это нормально:
auto &&temp_t = smart_owning<T>(new T);
auto &&temp_u = smart_owning<U>(new U);
f (temp_t, temp_u);
Обычно выражения, включающие столько подвыражений с вызовами функций, сколько f (smart_owning<T>(new T), smart_owning<U>(new U))
считаются разумными (это довольно простое выражение с точки зрения количества подвыражений).Запрещение таких выражений довольно раздражает и очень трудно оправдать.
[Это одна из причин, и, на мой взгляд, самая убедительная причина, почему недетерминизм порядка оценки был удален комитетом по стандартизации C ++ такчто такой код не является безопасным.(Это было проблемой не только для выделенной памяти, но и для любого управляемого выделения, такого как дескрипторы файлов, дескрипторы базы данных ...)]
Поскольку код часто нуждался в таких вещах, как smart_owning<T>(allocate_T())
в подвыраженияхи поскольку указание программистам разлагать умеренно сложные выражения, включающие в себя распределение во многих простых строках, не было привлекательным (больше строк кода не означает, что их легче читать), авторы библиотек предоставили простое исправление: функцию, позволяющую создатьобъект с динамическим временем жизни и создание собственного объекта вместе.Это решило проблему порядка оценки (но поначалу было сложно, потому что требовалась совершенная пересылка аргументов конструктора).
Предоставление двух задач функции (выделение экземпляра T
и экземпляраsmart_owning
) дает свободу проводить интересную оптимизацию: вы можете избежать одного динамического распределения, поместив управляемый объект и его владельца рядом друг с другом.
Но, опять же, это было не основное назначение таких функций, как make_shared
.
Поскольку интеллектуальные указатели с эксклюзивным владением по определению не должны вести подсчет ссылок и по определению не должны делитьсяданные, необходимые для удаления, либо между экземплярами, и, следовательно, могут хранить эти данные в «умном указателе» (*), для построения unique_ptr
дополнительное выделение не требуется;Тем не менее, был добавлен шаблон функции make_unique
, чтобы избежать проблемы с висящим указателем, а не оптимизировать небытие (выделение, которое не выполняется в первую очередь).
(*), что означает BTWуникальный владелец "умные указатели" делают не имеют указатель семантический, поскольку семантический указатель подразумевает, что вы можете делать копии "указателя", и вы не можете иметь две копии уникальноговладелец указывает на тот же экземпляр;«умные указатели» никогда не были указателями в любом случае, термин вводит в заблуждение.
Резюме:
make_shared<T>
выполняет необязательную оптимизацию , где нет отдельного динамического выделения памяти для T
: нет operator new(sizeof (T))
.Очевидно, что все еще создается экземпляр с динамическим временем жизни с другим operator new
: размещением new .
Если вы замените явное освобождение памяти явным уничтожением и добавите паузу сразу после этой точки:
class C {
public:
~C();
};
shared_ptr<C> r = make_shared<C>();
C *ar = r.get();
ar->~C();
pause(); // stops the program forever
Программа, вероятно, будет работать нормально; все еще нелогично, недопустимо, некорректно явно уничтожать объект, управляемый умным указателем .Это не "твой" ресурс.Если pause()
может выйти с исключением, умный указатель-владелец попытается уничтожить управляемый объект, который даже больше не существует.