std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));
Здесь есть много постов о Google и StackOverflow, но я не могу понять, почему make_shared
это более эффективно, чем прямое использование shared_ptr
.
Может кто-нибудь объяснить мне пошаговую последовательность созданных объектов и операций, выполняемых обоими, чтобы я смог понять, насколько make_shared
это эффективно. Я привел один пример выше для справки.
c++
c++11
shared-ptr
Ануп Бучке
источник
источник
make_shared
вы можете написать,auto p1(std::make_shared<A>())
и p1 будет иметь правильный тип.Ответы:
Разница в том, что
std::make_shared
выполняется одно выделение кучи, а при вызовеstd::shared_ptr
конструктора - два.Где происходит выделение кучи?
std::shared_ptr
управляет двумя организациями:std::make_shared
выполняет единый учет выделения кучи для пространства, необходимого как для блока управления, так и для данных. В другом случаеnew Obj("foo")
вызывает выделение кучи для управляемых данных, аstd::shared_ptr
конструктор выполняет другое для блока управления.Для получения дополнительной информации ознакомьтесь с примечаниями по реализации на cppreference .
Обновление I: Исключение-Безопасность
ПРИМЕЧАНИЕ (2019/08/30) : это не проблема начиная с C ++ 17, из-за изменений в порядке оценки аргументов функции. В частности, каждый аргумент функции должен полностью выполняться перед вычислением других аргументов.
Поскольку OP, кажется, задается вопросом о стороне безопасности исключений, я обновил свой ответ.
Рассмотрим этот пример,
Поскольку C ++ допускает произвольный порядок вычисления подвыражений, один из возможных порядков:
new Lhs("foo"))
new Rhs("bar"))
std::shared_ptr<Lhs>
std::shared_ptr<Rhs>
Теперь предположим, что мы получили исключение на шаге 2 (например, исключение из памяти,
Rhs
конструктор выдал какое-то исключение). Затем мы теряем память, выделенную на шаге 1, так как ничто не сможет очистить ее. Суть проблемы в том, что необработанный указатель не былstd::shared_ptr
сразу передан конструктору.Один из способов исправить это состоит в том, чтобы сделать их отдельными строками, чтобы это произвольное упорядочение не могло произойти.
Предпочтительный способ решить это, конечно, использовать
std::make_shared
вместо этого.Обновление II: недостаток
std::make_shared
Цитирую комментарии Кейси :
Почему экземпляры
weak_ptr
s поддерживают блок управления живым?У
weak_ptr
s должен быть способ определить, является ли управляемый объект еще действительным (например, дляlock
). Они делают это, проверяя количествоshared_ptr
s, которым принадлежит управляемый объект, который хранится в блоке управления. В результате блоки управления остаются активными до тех пор, покаshared_ptr
счетчик иweak_ptr
счетчик не достигнут значения 0.Вернуться к
std::make_shared
Поскольку
std::make_shared
выполняется одно выделение кучи как для блока управления, так и для управляемого объекта, невозможно освободить память для блока управления и управляемого объекта независимо. Мы должны подождать, пока мы не сможем освободить как блок управления, так и управляемый объект, что происходит до тех пор, пока не останетсяshared_ptr
s илиweak_ptr
s живыми.Предположим, что вместо этого мы выполнили два выделения кучи для блока управления и управляемого объекта через
new
иshared_ptr
конструктор. Затем мы освобождаем память для управляемого объекта (возможно, раньше), когда нетshared_ptr
живых s, и освобождаем память для блока управления (возможно, позже), когда нетweak_ptr
живых s.источник
make_shared
обратную сторону углового случая : поскольку имеется только одно распределение, память пуантиста не может быть освобождена, пока контрольный блок больше не используется. Аweak_ptr
может держать блок управления живым бесконечно.make_shared
иmake_unique
последовательно, у вас не будет необработанных указателей, и вы можете рассматривать каждое вхождениеnew
как запах кода.shared_ptr
, и нетweak_ptr
s, вызовreset()
наshared_ptr
экземпляр будет удален блок управления. Но это независимо от того,make_shared
был ли использован. Использованиеmake_shared
имеет значение, потому что это может продлить срок службы памяти, выделенной для управляемого объекта . Когдаshared_ptr
количество достигает 0, деструктор для управляемого объекта вызывается независимо от тогоmake_shared
, но освобождение его памяти может быть выполнено только в том случае, еслиmake_shared
он не использовался. Надеюсь, что это делает это более ясным.Общий указатель управляет как самим объектом, так и небольшим объектом, содержащим счетчик ссылок и другие служебные данные.
make_shared
может выделить один блок памяти для хранения обоих из них; Для создания общего указателя из указателя на уже выделенный объект потребуется выделить второй блок для хранения счетчика ссылок.Помимо этой эффективности, использование
make_shared
означает, что вам вообще не нужно иметь дело сnew
необработанными указателями, что обеспечивает лучшую безопасность исключений - нет возможности генерировать исключение после выделения объекта, но до назначения его интеллектуальному указателю.источник
Есть еще один случай, когда две возможности отличаются друг от друга, помимо уже упомянутых: если вам нужно вызвать непубличный конструктор (защищенный или закрытый), make_shared не сможет получить к нему доступ, в то время как вариант с новым работает нормально ,
источник
new
, иначе я бы использовалmake_shared
. Вот связанный вопрос об этом: stackoverflow.com/questions/8147027/… .Если вам нужно специальное выравнивание памяти на объекте, контролируемом shared_ptr, вы не можете полагаться на make_shared, но я думаю, что это единственная веская причина не использовать его.
источник
Я вижу одну проблему с std :: make_shared, он не поддерживает частные / защищенные конструкторы
источник
Shared_ptr
: Выполняет два выделения кучиMake_shared
: Выполняет только одно выделение кучиисточник
Что касается эффективности и времени, затрачиваемого на распределение, я сделал этот простой тест ниже, я создал много экземпляров с помощью этих двух способов (по одному за раз):
Дело в том, что использование make_shared заняло в два раза больше времени, чем использование new. Таким образом, при использовании new существует два выделения кучи вместо одного с использованием make_shared. Может быть, это глупый тест, но разве он не показывает, что использование make_shared занимает больше времени, чем использование new? Конечно, я говорю только об использованном времени.
источник
Я думаю, что вопрос безопасности исключений в ответе г-на Мпарка по-прежнему актуален. при создании shared_ptr, например: shared_ptr <T> (новый T), новый T может быть успешным, в то время как выделение блока управления shared_ptr может завершиться неудачей. в этом сценарии только что выделенный T будет течь, так как shared_ptr не может знать, что он был создан на месте, и его можно безопасно удалить. или я что-то упустил? Я не думаю, что более строгие правила оценки параметров функций здесь могут помочь
источник