Разница в том, что std::make_shared
выполняется одно выделение кучи, а при вызове std::shared_ptr
конструктора - два.
Где происходит выделение кучи?
std::shared_ptr
управляет двумя организациями:
- блок управления (хранит метаданные, такие как ref-count, стирание типа и т. д.)
- управляемый объект
std::make_shared
выполняет единый учет выделения кучи для пространства, необходимого как для блока управления, так и для данных. В другом случае new Obj("foo")
вызывает выделение кучи для управляемых данных, а std::shared_ptr
конструктор выполняет другое для блока управления.
Для получения дополнительной информации ознакомьтесь с примечаниями по реализации на cppreference .
Обновление I: Исключение-Безопасность
ПРИМЕЧАНИЕ (2019/08/30) : это не проблема начиная с C ++ 17, из-за изменений в порядке оценки аргументов функции. В частности, каждый аргумент функции должен полностью выполняться перед вычислением других аргументов.
Поскольку OP, кажется, задается вопросом о стороне безопасности исключений, я обновил свой ответ.
Рассмотрим этот пример,
void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }
F(std::shared_ptr<Lhs>(new Lhs("foo")),
std::shared_ptr<Rhs>(new Rhs("bar")));
Поскольку C ++ допускает произвольный порядок вычисления подвыражений, один из возможных порядков:
new Lhs("foo"))
new Rhs("bar"))
std::shared_ptr<Lhs>
std::shared_ptr<Rhs>
Теперь предположим, что мы получили исключение на шаге 2 (например, исключение из памяти, Rhs
конструктор выдал какое-то исключение). Затем мы теряем память, выделенную на шаге 1, так как ничто не сможет очистить ее. Суть проблемы в том, что необработанный указатель не был std::shared_ptr
сразу передан конструктору.
Один из способов исправить это состоит в том, чтобы сделать их отдельными строками, чтобы это произвольное упорядочение не могло произойти.
auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);
Предпочтительный способ решить это, конечно, использовать std::make_shared
вместо этого.
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));
Обновление II: недостаток std::make_shared
Цитирую комментарии Кейси :
Поскольку существует только одно распределение, память пуантиста не может быть освобождена, пока контрольный блок больше не используется. А weak_ptr
может держать блок управления живым бесконечно.
Почему экземпляры 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.