Main Content

AUTOSAR C++14 Rule A8-4-11

A smart pointer shall only be used as a parameter type if it expresses lifetime semantics

Since R2022b

Description

Rule Definition

A smart pointer shall only be used as a parameter type if it expresses lifetime semantics.

Rationale

A smart pointer manages the lifetime of a dynamically allocated object and destroys that object when the smart pointer goes out of scope. When you use a smart pointer as a parameter type for a function, you restrict that function to callers that use smart pointers, which is unnecessary if the function does not affect the lifetime of the managed object. In the case of shared smart pointers such as std::shared_ptr, your program incurs additional run-time overhead.

Examples of functions that affect the lifetime of a managed object include:

  • A function that transfers ownership of the managed object from the std::unique_ptr smart pointer parameter to a different std::unique_ptr smart pointer through a move operation.

  • A function that copies the std::shared_ptr smart pointer parameter inside the function body, sharing the ownership of the managed object with another std::shared_ptr smart pointer.

If the function does not affect the lifetime of the managed object, pass the object by reference or as a raw pointer instead.

If that object is managed by a non-local smart pointer, check that both these conditions are true:

  • The object is not reset or reassigned inside a function that is down the call tree of the called function.

  • The managed object is not destroyed before it is dereferenced.

For instance, if the object is managed by a non-local shared pointer, make a local copy inside the called function.

The use of a non-owning smart pointer as a parameter type, such as std::observer_ptr, is compliant with this rule. See std::experimental::observer_ptr.

Polyspace Implementation

Polyspace® reports a violation of this rule when you use smart pointers std::shared_ptr or std::unique_ptr as parameter types in a user-defined function that does not affect the lifetime of those smart pointers in the function body.

Polyspace considers the lifetime of an std::shared_ptr smart pointer parameter affected if, along at least one execution path:

  • The pointer is copied through a copy constructor or a copy assignment.

  • The pointer is moved-from through a move constructor or move assignment. This operation involves a call to std::move.

  • The pointer is passed as an argument to a function with one of these parameter types:

    • An std::shared_ptr<T> type. In this case the copy constructor is invoked.

    • A const std::shared<T>& type. This const lvalue reference indicates that the function retains a reference count and might make a copy of the pointer.

    • An std::shared<T>& type. This lvalue reference indicates that the passed parameter will be modified.

  • A modifier member function such as std:shared_ptr::reset or a modifier non-member function such as std::shared_ptr::swap is called on the pointer. For a list of modifier functions, see std::shared_ptr.

  • The pointer is the destination of a copy or move assignment.

const-qualified std::shared_ptr smart pointer parameters cannot be modified.

Polyspace considers the lifetime of an std::unique_ptr smart pointer parameter affected if, along at least one execution path:

  • The pointer is moved-from with a call to std::move.

  • The pointer is moved-from through a move constructor or a move assignment.

  • The pointer is passed as an argument to a function with one of these parameter types:

    • An std::unique_ptr<T> type. In this case, the move constructor is invoked.

    • An std::unique<T>& type. This lvalue reference indicates that the function modifies the passed parameter.

  • A modifier member function such as std::unique_ptr::reset or a modifier non-member function such as std::unique_ptr::swap is called on the pointer. For a list of modifier functions, see std::unique_ptr.

  • The pointer is the destination of a move assignment.

const-qualified std::unique_ptr smart pointer parameters cannot be moved-from or modified and their lifetimes are not affected. The lifetime of the passed argument is also not affected in the case of the const lvalue reference parameter const std::unique_ptr<T>&.

Polyspace does not report a violation of this rule when you use smart pointers as parameter types in these cases:

  • The pointer is used as a parameter of a template function. The parameter types of a template function depend on the instantiation of the function and a fix is not always possible at the template design level. For instance, the use of the shared pointer ptr is not reported as a violation in this code.

    #include <iostream>
    #include <memory>
    #include <cassert>
    
    template <typename T>
    double XtimesY(T ptr) // Use of ptr not flagged
    {
      return (ptr->x) * (ptr->y);
      // ...
    }
    struct S
    {
      double x;
      double y;
      S(double x0, double y0) : x{x0}, y{y0} {}
    };
    void func()
    {
      auto a = new S{100.0, 200.0};
      auto a_cpy = std::shared_ptr<S>(a);
      assert(XtimesY(a) == XtimesY(a_cpy));
    }

  • The smart pointer parameter is captured by a lambda function inside the function body. For instance, Polyspace does not report a violation when you use the shared pointer ptr as a parameter of func() in this code. The parameter is captured by a lambda function inside the body of func(). Polyspace reports the use of lambda function parameters sp1 and up1 as violations because their lifetime is not modified inside the lambda function.

    #include <memory>
    
    struct S
    {
      double x;
      double y;
      S(double x0, double y0) : x{x0}, y{y0} {}
    };
    
    void func(std::unique_ptr<S> ptr)
    { // no defect is detected on 'ptr'
      auto lambdaF =
          [&ptr0 = ptr]            // 'ptr' is captured (by reference)
          (std::shared_ptr<S> sp1, // Non-compliant
           std::unique_ptr<S> up1) // Non-compliant
      {                            // lifetime of 'sp1' and 'up1' not affected
        // ...
      };
      auto a = std::make_shared<S>(1.0, 2.0);
      auto b = std::make_unique<S>(100.0, 200.0);
      lambdaF(a, std::move(b));
    }

Troubleshooting

If you expect a rule violation but Polyspace does not report it, see Diagnose Why Coding Standard Violations Do Not Appear as Expected.

Examples

expand all

#include <memory>
#include <cassert>
#include <iostream>
#include <string>

class A
{
};

//

void ptr_is_moved_from(
    std::shared_ptr<A> ptr,       // Compliant
    const std::shared_ptr<A> ptr1 // Compliant
)
{

  std::shared_ptr<A> destinationPtr = std::move(ptr);
  destinationPtr = std::move(ptr1); // copy takes place
}

//


void ptr_param_is_lvalue_ref(std::shared_ptr<A> &ptr) 
{
  ptr.reset(new A()); // ptr is modified by member function
}
void pass_to_function_with_lvalue_ref_param(
    std::shared_ptr<A> ptr,       // Compliant
    const std::shared_ptr<A> ptr1 // Non-compliant
)
{

  ptr_param_is_lvalue_ref(ptr);
#if 0
        // compilation error
        use_lvalue_reference_shared_ptr(ptr1); //ptr1 is const-qualified and cannot be modified
#endif
}

//

void member_function_modifies_ptr(
    std::shared_ptr<A> ptr,       // Comliant
    const std::shared_ptr<A> ptr1 // Non-compliant
)
{
  ptr.reset();
#if 0
        // compilation error
        ptr1.reset(); //ptr1 is const-qualified and cannot be modified

#endif
}

//

void ptr_is_destination_of_copy_move(
    std::shared_ptr<A> ptr,       // Comliant
    const std::shared_ptr<A> ptr1 // Non-compliant
)
{

  std::shared_ptr<A> gPtr0 = std::make_shared<A>();
  ptr = gPtr0; // ptr destination of copy assignment
#if 0
        // compilation error
        ptr1 = gPtr0; // ptr1 is const-qualified and cannot be modified
#endif
}

In this example, Polyspace reports the use of the shared pointer parameter const std::shared_ptr<A> ptr1 as a violation because the parameter is const-qualified and cannot be modified. The lifetime of the parameter is not affected inside these functions and running the program results in a compilation error.

  • Function pass_to_function_with_lvalue_ref_param()

  • Function member_function_modifies_ptr()

  • Function ptr_is_destination_of_copy_move()

Polyspace does not report the use of const std::shared_ptr<A> ptr1 in the function ptr_is_moved_from() because the call to std::move creates a copy, which modifies the lifetime of ptr1 by incrementing the reference count.

Polyspace does not report the use of the shared pointer parameter std::shared_ptr<A> ptr in these functions because, in each case, the lifetime of the smart pointer is affected:

  • In the function ptr_is_moved_from(), the managed object is moved-from.

  • In the function pass_to_function_with_lvalue_ref_param(), the pointer is passed to the function ptr_param_is_lvalue_ref() as an lvalue reference and is then modified inside that function by member function reset().

  • In the function member_function_modifies_ptr(), the pointer is modified by member function reset().

  • In the function ptr_is_destination_of_copy_move(), the pointer is the destination of a copy assignment.

#include <memory>
#include <cassert>
#include <iostream>
#include <string>

static int level = 0;
class A
{
};
void ptr_is_moved_from(
    std::unique_ptr<A> ptr,       // compliant
    const std::unique_ptr<A> ptr1 // non-compliant
)
{

  std::unique_ptr<A> destinationPtr = std::move(ptr);
#if 0
        // compilation error
        destinationPtr = std::move(ptr1); //Cannot be copied
#endif
}

//

void ptr_param_is_lvalue_ref(std::unique_ptr<A> &ptr)
{
  ptr.reset(new A());
}

void ptr_param_is_const_lvalue_ref(const std::unique_ptr<A> &ptr) // compliant
{
  ++level;
  std::cout << std::string(level, '\t') << __FUNCTION__ << "\n";
  std::cout << std::string(level, '\t') << "ptr " << ptr.get() << "\n";
  std::cout << std::string(level, '\t') << __FUNCTION__ << "\n";
  --level;
}

void pass_input_unique_ptr_as_lvalue_reference(
    std::unique_ptr<A> ptr,        // compliant
    const std::unique_ptr<A> ptr1, // non-compliant
    std::unique_ptr<A> ptr2,       // non-compliant
    const std::unique_ptr<A> ptr3  // non-compliant
)
{
  ptr_param_is_lvalue_ref(ptr);

#if 0
        // compilation error
        ptr_param_is_lvalue_ref(ptr1);
#endif

  ptr_param_is_const_lvalue_ref(ptr2); // ptr2 passed as const lvalue reference, lifetime not affected

  ptr_param_is_const_lvalue_ref(ptr3); // ptr3 passed as const lvalue reference, lifetime not affected
}

//

void ptr_modified_by_member_function(
    std::unique_ptr<A> ptr,       // compliant
    const std::unique_ptr<A> ptr1 // non-compliant
)
{
  ptr.release();
#if 0
        // compilation error
        ptr1.release();
#endif
}

//

void ptr_is_destination_of_copy_move(
    std::unique_ptr<A> ptr,       // compliant
    const std::unique_ptr<A> ptr1 // non-compliant
)
{
  std::unique_ptr<A> gPtr0 = std::make_unique<A>();

  ptr = std::move(gPtr0);
#if 0
        // compilation error
        ptr1 = std::move(gPtr0);

#endif
}

In this example, Polyspace reports the use of the unique pointer parameter const std::unique_ptr<A> ptr1 because the parameter is const-qualified and cannot be modified. The lifetime of the parameter is not affected inside these functions and attempts to modify the parameter results in compilation errors.

  • Function ptr_is_moved_from().

  • Function pass_input_unique_ptr_as_lvalue_reference().

  • Function ptr_modified_by_member_function().

Similarly, Polyspace reports the use of the parameters std::unique_ptr<A> ptr2 and const std::unique_ptr<A> ptr3 because they are passed to the function ptr_param_is_const_lvalue_ref(), which does not affect the lifetime of these parameters.

Polyspace does not report the use of the unique pointer parameter std::unique_ptr<A> ptr in these functions because, in each case, the lifetime of the smart pointer is affected:

  • In the function ptr_is_moved_from(), the unique pointer is moved-from.

  • In the function pass_input_unique_ptr_as_lvalue_reference(), the pointer is passed to the function ptr_param_is_lvalue_ref() as an lvalue reference and is then modified inside that function by member function reset().

  • In the function ptr_modified_by_member_function(), the pointer is modified by member function release().

  • in the function ptr_is_destination_of_copy_move(), the pointer is the destination of a move assignment.

Check Information

Group: Declarators
Category: Required, Automated

Version History

Introduced in R2022b