Ответ 1
Существует действительно существенное различие между:
shared_ptr<T> sp(new T());
и
shared_ptr<T> sp = make_shared<T>();
Первая версия выполняет выделение для объекта T
, затем выполняет отдельное выделение для создания контрольного счетчика. Вторая версия выполняет одно выделение как для объекта, так и для счетчика ссылок, помещая их в смежную область памяти, что приводит к меньшему издержкам памяти.
Кроме того, некоторые реализации могут выполнять дальнейшие оптимизации пространства в случае make_shared<>
(см. оптимизацию "Мы знаем, где вы живете", выполняемые MS-реализацией).
Однако это не единственная причина, по которой существует make_shared<>
. Версия, основанная на явном new T()
, не является исключением в некоторых ситуациях, особенно при вызове функции, принимающей shared_ptr
.
void f(shared_ptr<T> sp1, shared_ptr<T> sp2);
...
f(shared_ptr<T>(new T()), shared_ptr<T>(new T()))
Здесь компилятор может оценить первое выражение new T()
, затем оценить второе выражение new T()
, а затем построить соответствующие объекты shared_ptr<>
. Но что, если второе распределение вызывает исключение, прежде чем первый выделенный объект привязан к его shared_ptr<>
? Это будет утечка. С make_shared<>()
это невозможно:
f(make_shared<T>(), make_shared<T>())
Поскольку выделенные объекты привязаны к соответствующим объектам shared_ptr<>
внутри каждого вызова функции make_shared<>()
, этот вызов является безопасным для исключений. Это еще одна причина, по которой голый new
никогда не должен использоваться, если вы действительно не знаете, что делаете. (*)
Учитывая ваше замечание о reset()
, вы правы в том, что reset(new T())
будет выполнять отдельные распределения для счетчика и объекта, точно так же, как построение нового shared_ptr<>
будет выполнять отдельное выделение, когда необработанный указатель передается в качестве аргумента. Поэтому предпочтительным является назначение с использованием make_shared<>
(или даже оператор, такой как reset(make_shared<T>())
).
Независимо от того, поддерживает или не поддерживает reset()
список вариационных аргументов, это, вероятно, скорее своего рода открытое обсуждение, для которого StackOverflow не подходит.
(*) Есть несколько ситуаций, которые по-прежнему требуют этого. Например, тот факт, что в стандартной библиотеке С++ отсутствует соответствующая функция make_unique<>
для unique_ptr
, поэтому вам придется написать ее самостоятельно. Другая ситуация заключается в том, что вы не хотите, чтобы объект и счетчик были выделены на одном блоке памяти, поскольку наличие слабых указателей на объект будет препятствовать освобождению целого блока, даже если не существует более владеющих указателей на объект.