V824. It is recommended to use the 'make_unique/make_shared' function to create smart pointers.
The analyzer recommends that you create a smart pointer by calling the 'make_unique' / 'make_shared' function rather than by calling a constructor accepting a raw pointer to the resource as a parameter.
Using these functions has the following advantages:
- the code is made clearer by removing explicit calls of the 'new' operator for dynamic allocations (smart pointers in themselves remove explicit calls of the 'delete' operator);
- better security in case of exception;
- optimized object allocation.
Consider the following example:
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 } });
}
Since the standard does not define a evaluation order of function arguments, the compiler may choose the following order for the sake of optimization:
- Call 'new int { 0 }'
- Call 'new int { 1 }'
- The first call of the 'std::unique_ptr<int>' constructor
- The second call of the 'std::unique_ptr<int>' constructor
Now, if the second call of 'new' throws an exception, a memory leak will occur as the resource allocated by the first call of 'new' will never be freed. Using the 'make_unique' function to create the pointer helps solve this problem by guaranteeing the freeing of memory if an exception occurs.
Optimized version:
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));
}
The C++17 standard, while still not specifying the exact evaluation order for arguments, provides additional guarantees. All side effects of a function argument must be evaluated before the next argument is evaluated. This helps mitigate the risk in case of exceptions, but it is still preferable to use 'make_unique'.
One thing should be noted about the 'make_shared' function. When it is used, the pointer's control block is allocated next to the managed object. This helps reduce the number of dynamic allocations and optimize the use of the CPU cache.
The object gets deleted when the reference counter reaches zero, but the control block exists as long as there are existing weak references to the pointer. If both the control block and the managed object were created using the 'make_shared' function (i.e. allocated in the same memory block), the program will not be able to deallocate that memory as long as the reference counter is at zero and there is at least one 'weak_ptr' to the object. This may be unwanted behavior with large objects. If you intentionally avoid using the 'make_shared' function to avoid having both the control block and the object getting allocated in the same memory block, you can suppress the warning.
There is a restriction concerning the use of different versions of the C++ standard: as the functionality of 'make_unique' and 'make_shared' has changed several times since C++11, the diagnostic's behavior depends on the standard's version as follows:
- C++11: the analyzer suggests replacing object allocation and subsequent passing to the 'shared_ptr' constructor with the 'make_shared' function.
- C++14 or higher: the analyzer additionally suggests replacing allocation of a single object or array of objects with the 'make_unique' function.
- C++20 or higher: the analyzer additionally suggests replacing the 'shared_ptr' constructor with the 'make_shared' function for arrays of objects as well.