This rule is part of MISRA C++:2023.
Usage of this content is governed by Sonar’s terms and conditions. Redistribution is
prohibited.
Rule 23.11.1 - The raw pointer constructors of std::shared_ptr and std::unique_ptr should not be used
[unique.ptr]
[util.smartptr.shared]
Category: Advisory
Analysis: Decidable,Single Translation Unit
Amplification
This rule applies to the use of the constructors of std::shared_ptr and std::unique_ptr that take ownership of the raw
pointer passed as an argument.
Rationale
The functions std::make_shared and std::make_unique perform two operations at the same time:
- Creating an object dynamically (equivalent to
new); and
- Creating a smart pointer that will manage the newly created object’s lifetime.
Performing both operations in one step ensures that there is no interleaved operation that could throw an exception before the smart pointer has
taken ownership of the object. This also prevents two unique_ptr or unrelated shared_ptr instances from "owning" the same
object.
Notes:
-
std::make_shared will allocate a single memory area for both the object and the bookkeeping data required for shared pointers (the
reference counts). While this is usually beneficial in terms of performance, it has the drawback that the memory for the object will not be
reclaimed when the last shared_ptr pointing to it is destroyed, but only when all weak_ptr references to the object are
also destroyed. If this behaviour is undesirable, a custom variant of std::make_shared can be provided that omits this optimisation.
- Since C++17, the evaluation order of function calls has been made stricter and some of the issues with interleaved calls can no longer happen.
However, the use of
make_shared or make_unique is still clearer and can result in better performance.
Example
struct A { int8_t i; };
class B { };
void f0()
{
auto p = std::make_shared< A > (); // Compliant
int8_t * pi = &( p->i );
std::shared_ptr< int8_t > q ( p, pi ); // Does not apply - not taking ownership
}
auto f1()
{
auto * p1 = new A ();
auto p2 = std::make_unique< A >(); // make_unique may throw
return std::shared_ptr< A >( p1 ); // Non-compliant - memory leak if
} // make_unique throws
auto f2( std::unique_ptr< A > p )
{
auto q = p.get();
// ...
return std::unique_ptr< A >( q ); // Non-compliant - causes double delete
}
void f3( std::shared_ptr< A > a, std::shared_ptr< B > b );
void f4()
{
f3( std::shared_ptr< A >( new A() ),
std::shared_ptr< B >( new B() ) ); // Non-compliant - but well defined
} // in C++17
Prior to C++ 17, a possible sequencing for the operations in the call to f3, where $n represents an object in the
abstract machine, was:
-
new A() -> $1
-
new B() -> $2
-
std::shared_ptr< A >( $1 ) -> $3
-
std::shared_ptr< B >( $2 ) -> $4
-
f3( $3, $4 )
If an exception is thrown during the construction of B, the object of type A will leak. This does not happen in the
following as there are no interleaving operations:
void f5()
{
f3( std::make_shared< A >(),
std::make_shared< B >() ); // Compliant
}
Copyright The MISRA Consortium Limited © 2023