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' также и для массива объектов.