>
>
>
V824. It is recommended to use the 'mak…


V824. It is recommended to use the 'make_unique/make_shared' function to create smart pointers.

Анализатор рекомендует создать умный указатель не путем вызова конструктора, принимающего "сырой" указатель на ресурс, а вызовом функции 'make_unique' / 'make_shared'.

Использование этих функций позволяет:

  • улучшить читаемость кода, устраняя явные вызовы оператора 'new' для динамических аллокаций (сами умные указатели устраняют явный вызов оператора 'delete');
  • повысить безопасность кода в случае броска исключения;
  • может оптимизировать размещение объектов в памяти.

Рассмотрим пример кода:

void foo(std::unique_ptr<int> a, std::unique_ptr<int> b)
{
  ....
}

void bar()
{
  foo( std::unique_ptr<int> { new int { 0 } },
       std::unique_ptr<int> { new int { 1 } });
}

Поскольку стандартом не регламентируется порядок вычисления аргументов функции, компилятор в целях оптимизации может сделать это в такой последовательности:

  • Вызов 'new int { 0 }'
  • Вызов 'new int { 1 }'
  • Первый вызов конструктора 'std::unique_ptr<int>'
  • Второй вызов конструктора 'std::unique_ptr<int>'

В этом случае, если второй вызов 'new' бросит исключение, произойдет утечка памяти – ресурс, выделенный первым вызовом 'new', никогда не будет освобожден. Создание указателя при помощи 'make_unique' решает эту проблему, гарантируя освобождение памяти в случае броска исключения.

Оптимизированный код:

void foo(std::unique_ptr<int> a, std::unique_ptr<int> b)
{
  ....
}

void bar()
{
  foo( std::make_unique<int>(0), std::make_unique<int>(1));
}

Начиная с C++17, хотя порядок вычисления аргументов остается неуточненным, вводятся дополнительные гарантии. Все побочные эффекты от аргумента функции должны быть вычислены до того, как произойдет вычисление следующего аргумента. Это снижает риски в случае исключений, но использовать 'make_unique' все равно предпочтительно.

Замечание по поводу 'make_shared'. При использовании этой функции контрольный блок указателя размещается в памяти рядом с объектом. Это уменьшает количество динамических аллокаций и оптимизирует использование кэша процессора.

Объект удаляется, когда счетчик ссылок становится нулевым, но контрольный блок существует до тех пор, пока существуют слабые ссылки на указатель. Если контрольный блок и объект были созданы при помощи 'make_shared' (т.е. размещены в одной области памяти), это приводит к тому, что память не может быть освобождена до тех пор, пока счетчик ссылок нулевой и на объект ссылается хотя бы один 'weak_ptr'. Для больших объектов такое поведение может быть нежелательным. Если функция 'make_shared' не используется сознательно, чтобы избежать размещения контрольного блока в одной области памяти с объектом, срабатывание диагностики можно подавить.

Ограничение, связанное с разными версиями стандарта C++: так как возможности функций 'make_unique' и 'make_shared' менялись, начиная с C++11, диагностика зависит от версии стандарта следующим образом:

  • C++11: анализатор предлагает заменять аллокацию объекта и его последующую передачу в конструктор 'shared_ptr' на функцию 'make_shared'.
  • C++14 или выше: анализатор дополнительно предлагает заменять аллокацию одного объекта или массива объектов на функцию 'make_unique'.
  • C++20 или выше: анализатор дополнительно предлагает заменить конструктор 'shared_ptr' на функцию 'make_shared' также и для массива объектов.