Подтвердить что ты не робот

Лучше ли использовать shared_ptr.reset или operator =?

Я пытаюсь обернуть голову вокруг новых идиом для С++ 11.

Похоже, что с shared_ptr, по крайней мере, существует существенное различие между использованием new T() и make_shared<T>().

Но как сбросить общий указатель, чтобы указать на какой-то новый экземпляр. Раньше я обычно использовал член reset(new T()). Однако разве это не связано с той же проблемой, что и при использовании make_shared()? (т.е. он не позволяет make_shared выделять объект, поэтому он вынужден помещать счетчик ref в отдельном распределении вместо того же самого распределения, что и сам T)

Лучше ли вам лучше использовать:

   mysharedptr = make_shared<T>(args...);

Или есть лучший способ?

И не должен reset предлагать переменную пересылку аргументов как make_shared, так что можно было бы написать mysharedptr.reset(args...);?

4b9b3361

Ответ 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, поэтому вам придется написать ее самостоятельно. Другая ситуация заключается в том, что вы не хотите, чтобы объект и счетчик были выделены на одном блоке памяти, поскольку наличие слабых указателей на объект будет препятствовать освобождению целого блока, даже если не существует более владеющих указателей на объект.

Ответ 2

Правильно, reset(new T...) страдает все проблемы shared_ptr(new T...); это приведет к двойному распределению, а также небезопасно (не так много шансов на утечку, если bad_alloc не происходит внутри reset).

reset документируется как эквивалент shared_ptr<T>(ptr).swap(*this), поэтому вы также можете написать:

make_shared<T>(args...).swap(mysharedptr);

Назначение из make_shared<T> почти эквивалентно, единственное отличие - относительный порядок удаления старого T и уничтожение временного shared_ptr, который не наблюдается.