Main Content

AUTOSAR C++14 Rule A8-4-12

A std::unique_ptr shall be passed to a function as: (1) a copy to express the function assumes ownership (2) an lvalue reference to express that the function replaces the managed object.

Since R2022b

Description

Rule Definition

A std::unique_ptr shall be passed to a function as: (1) a copy to express the function assumes ownership (2) an lvalue reference to express that the function replaces the managed object.

Rationale

You use an std::unique_ptr smart pointer to indicate that only one smart pointer owns the managed object. A smart pointer manages the lifetime of a dynamically allocated object and destroys that object when the smart pointer goes out of scope. Pass an std::unique_ptr smart pointer to a function in one of these ways:

  • If you expect the function to take ownership of the managed object, pass the smart pointer by value. For instance, the std::unique_ptr argument of the caller in this code is moved-from and ownership of the int object is transferred to an implicitly constructed std::unique_ptr smart pointer, which is then passed to the function func().

    void func(std::unique_ptr<int>) {}
    void otherFunc()
    {
        auto smartPtr = make_unique<int>(1);
        func(std::move(smartPtr)); //caller
    }
    

  • If you expect the function to replace the object that the smart pointer manages, pass the smart pointer as an lvalue reference. For instance, you might replace the managed object by calling the assignment operator or the reset() smart pointer member function. For example, uniquePtr is reset inside func() and no longer manages the int object in this code.

    void func(std::unique_ptr<int>& smartPtr)
    {
        smartPtr.reset();
    }
    void otherFunc
    {
        auto uniquePtr = make_unique<int>(1);
        func(std::move(uniquePtr); //caller
    }
    If you do not intend to replace the managed object, pass an lvalue reference to the managed object itself.

You cannot move or reset a const-qualified lvalue reference to an std::unique_ptr smart pointer. If you do not intend to modify the managed object, pass a const-qualified lvalue reference to the managed object instead.

There is no violation of this rule if you pass an rvalue reference to an std::unique_ptr smart pointer and the reference is moved into a different std::unique_ptr smart pointer inside the function. For instance, rvalueRefPtr is passed by rvalue reference to an std::unique_ptr smart pointer and then moved into a different smart pointer std::unique_ptr newPtr.

void func(std::unique_ptr<int>&& rvalueRefPtr)
{
    std::unique_ptr<int> newPtr(std::move(rvalueRefPtr));
}

Polyspace Implementation

Polyspace® the use of a parameter passed as an lvalue reference to std::unique_ptr if that parameter is not reset.

Polyspace considers the parameter reset in these cases:

  • The parameter is used as the destination of a move assignment.

  • The parameter is moved-from through a call to std::move.

  • The parameter is used as the argument of std::unique_ptr member functions reset(), swap(), or release().

  • The parameter is used as the argument of any function that takes a non-const qualified lvalue reference to an std::unique_ptr smart pointer, regardless of the implementation of the callee.

Polyspace also reports the use of const-qualified lvalue references to an std::unique_ptr smart pointer because you cannot modify the pointer and transfer ownership of the managed object.

Polyspace does not report the use of smart pointers as parameter types in these cases:

  • The pointer is used as a parameter of a template function. The parameter type of template functions depends on the instantiation of the function and a fix is not always possible at the template design level. For instance, Polyspace does not report the use of unique pointer ptr as parameter 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 = std::make_unique<S>{100.0, 200.0};
      auto b = new S(100.0, 200.0);
      assert(XtimesY(a) == XtimesY(b));
    }

  • 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 an lvalue reference to unique pointer ptr as a parameter of func() because it is captured by a lambda function inside the body of func(). Polyspace reports the use of lambda function parameter up1 because its 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::unique_ptr<S> up1) // Non-compliant
      {                            // lifetime of 'up1' not affected
        // ...
      };
      auto b = std::make_unique<S>(100.0, 200.0);
      lambdaF(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>

void func1(std::unique_ptr<int>& uniquePtrParam) // Non-compliant
{ 
  // uniquePtrParam not reset or modified
}

void func2(const std::unique_ptr<int>& uniquePtrParam) // Non-compliant
{ 
  // const lvalue reference cannot be modified
}

void func_param_lvalue_ref(std::unique_ptr<int>& uniquePtrParam) // Compliant
{ 
  std::unique_ptr<int> newPtr = std::make_unique<int>(-8);
  std::swap(newPtr, uniquePtrParam);
}

void func_param_const_lvalue_ref(const std::unique_ptr<int>& uniquePtrParam);

void func3(std::unique_ptr<int>& uniquePtrParam) // Compliant
{ 
  // uniquePtrParam passed to function with lvalue reference
  // to std::unique_ptr parameter

  func_param_lvalue_ref(uniquePtrParam);
}

void func4(std::unique_ptr<int>& uniquePtrParam) // Non-compliant
{ 
  // uniquePtrParam passed to function with const lvalue reference
  // to std::unique_ptr parameter. Function cannot modify managed object

  func_param_const_lvalue_ref(uniquePtrParam);
}

In this example, Polyspace reports the use of uniquePtrParam as a parameter to these functions: v

  • func1() — The lvalue reference is not modified or reset inside the function.

  • func2() — The lvalue reference is const-qualified and cannot be modified.

  • func4() — The lvalue reference parameter is passed as an argument to function func_param_const_lvalue_ref(), which has a const-qualified parameter. This indicates the passed parameter will not be modified inside that function.

If you do not intend to transfer the ownership of the managed object or to reset the std::unique_ptr smart pointer, pass an lvalue reference to the managed object itself instead.

Polyspace does not report the use of the std::unique_ptr lvalue reference as a parameter of func_param_lvalue_ref() because the parameter is used as an argument of swap() and the ownership of the managed object is transferred.

Similarly, Polyspace does not report the use of the parameter of func3() because it is used as an argument of the function func_param_lvalue_ref(). The function func_param_lvalue_ref() takes an non-const lvalue reference to an std::unique_ptr smart pointer as a parameter which indicates it might modify that parameter.

Polyspace does not report the use of the parameter of func3() regardless of the implementation of func_param_lvalue_ref().

Check Information

Group: Declarators
Category: Required, Automated

Version History

Introduced in R2022b