Різниця полягає в тому, що він 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 через зміни в порядку оцінки аргументів функції. Зокрема, кожен аргумент функції потрібно повністю виконати перед оцінкою інших аргументів.
Оскільки ОП, здається, цікавить питання щодо виключень, що стосуються безпеки, я оновив свою відповідь.
Розглянемо цей приклад,
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
або weak_ptr
s.
Припустимо, ми замість цього виконали два купі-виділення для керуючого блоку та керованого об'єкта через new
та shared_ptr
конструктор. Тоді ми звільняємо пам'ять керованого об'єкта (можливо, раніше), коли немає shared_ptr
живих, і звільняємо пам'ять для блоку управління (можливо, пізніше), коли немає weak_ptr
живих.