Main Content

AUTOSAR C++14 Rule A20-8-4

A std::unique_ptr shall be used over std::shared_ptr if ownership sharing is not required

Since R2022b

Description

Rule Definition

A std::unique_ptr shall be used over std::shared_ptr if ownership sharing is not required.

Rationale

A resource can be owned by a single std::unique_ptr object. In contrast, a resource can be shared by several std::shared_ptr objects. Using std::unique_ptr when resource sharing is not required has these advantages:

  • An std::shared_ptr object keeps an internal count of objects that share a resource. Copy assigning or copy constructing an std::shared_ptr object increments this use count. An std::unique_ptr does not keep this count, making it a more efficient alternative.

  • The resource owned by an std::unique_ptr object has a predictable life cycle. Unless its ownership is moved to a different std::unique_ptr object, the resource is released when the object goes out of scope. The resource managed by an std::shared_ptr object has an unpredictable life cycle. It is not released until all the objects that share it are out of scope.

Using std::shared_ptr objects makes your code inefficient and difficult to debug. Avoid using std::shared_ptr objects unless ownership sharing is required. Instead, use std::unique_ptr objects as smart pointers.

Polyspace Implementation

Polyspace® raises a violation of this rule when replacing a std::shared_ptr object in your code by an std::unique_ptr object is possible. For instance, a violation is raised when either of these conditions are true:

  • An std::shared_ptr is not used in a copy construction or copy assignment. The internal reference count remains at one. Consider this code:

    #include <memory>
    class A();
    void bar(std::shared_ptr<A> sp);
    void foo(){
    auto sp = std::make_shared<A>();//count==1
    bar(sp);
    }
    No std::shared_ptr objects are copy constructed or copy assigned. The object sp does not share its resources. Declaring this object as an std::unique_ptr might be more efficient. See Avoid Using shared_ptr Objects If They are Not Copied.

  • std::shared_ptr objects are sequentially copied to create a chain of N shared pointers, but the first (N-1) shared pointers are not dereferenced or passed to a function. Because these (N-1) shared pointers are not used, moving their resources and declaring the pointers as unique pointers might be more efficient. For instance, in this code:

    #include <memory>
    class obj();
    void foo(){
    	auto A = std::make_shared<obj>();
    	auto B = A;//count==2
    	auto C = B;//count==3
    	//…
    	auto X = *C;
    }
    A chain of shared pointers is created by copying A into B and then copying B into C. Because A and B are not dereferenced, declaring A, B, and C as std::unique_ptr objects does not result in loss of functionality and makes the code more efficient. See Avoid Using shared_ptr Objects When They Are Copied Once But Not Dereferenced.

As an exception, Polyspace does not raise a violation if you use an std::shared_ptr object as an argument of certain standard atomic operation.

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 <cstdint>
#include <thread>

struct A {
	A(std::uint8_t xx, std::uint8_t yy) : x(xx), y(yy) {}
	std::uint8_t x;
	std::uint8_t y;
};
void Bar(std::shared_ptr<A> obj) {/**/ }

void Func1()
{
	auto spA = std::make_shared<A>(3,5); //Noncompliant
}

void Func2(void)
{
	std::shared_ptr<A> spA = std::make_shared<A>(3,5);
	//Compliant, object accesses in parallel
	std::thread th1{&Bar, spA};
	std::thread th2{&Bar, spA};
	th1.join();
	th2.join();
}

In this example:

  • An std::shared_ptr object spA is declared in Func1() but it does not share its resources. Polyspace flags spA as noncompliant. The best practice is to declare such pointers as std::unique_ptr objects.

  • An std::shared_ptr object spA is declared in Func2() and then used in two different threads simultaneously. Polyspace does not raise a violation because spA is used in multiple threads at the same time.

#include <memory>
#include <cstdint>
#include <thread>
#include<iostream>

class Resource {
public:
	Resource(){}
};
class Interface{
public:
	Interface(std::shared_ptr<Resource> obj):R(obj){}
	std::shared_ptr<Resource> R;
};
void foo(){
	auto user1 = std::make_shared<Resource>();//Noncompliant
	auto user2 = user1;
	auto user3 = user2;
	Interface ti1(user3);    
}
void foo2(){
	auto user1 = std::make_shared<Resource>();//Compliant
	auto user2 = user1; 
	auto user3 = user2;
	//...
	auto X = *user2;
}
void bar(){
	auto user1 = std::make_unique<Resource>();
	auto user2 = std::move(user1);
	auto user3 = std::move(user2);
	//Interface ti1(user3);
}

In this example, a cheap interface class Inteface contains a pointer to an expensive object of class Resource.In foo(), three shared pointers user1, user2, and 'user3 are created by copying user1 into user2 and then copying user2 into user3. The developer intent might be to create three separate Interface objects by using these shared pointers. Perhaps inadvertently, only one Interface object is created by using user3. Because user1 and user2 do not access their resource, it is possible to move the resource from one pointer to the other until user3 owns the resource. It might be more efficient to replace these shared pointers in by unique pointers, as shown in bar(). Polyspace raises a violation on foo().

The function foo2() contains a similar chain of copied shared pointers. Because user2 accesses its resources, moving its resources to user3 results in loss of functionality. In this case, it is not possible to replace the shared pointers by unique pointers. Polyspace does not raise a violation.

#include <memory>
class A{A();}; 
extern void foo(std::shared_ptr<A> sp);
void func1(){
	auto var = std::make_shared<A>();//Noncompliant
	foo(var);
	//...
}

void func2(){
	foo(std::make_shared<A>());//Compliant
	//...
}

void func3(){
	auto up = std::make_unique<A>();
	foo (std::shared_ptr<A>(std::move(up)));//Compliant
}

In this example, the external function foo() accepts an std::shared_ptr as input. In func1, to call foo(), the std::shared_ptr object var is created, but this object does not share ownership of its resources. Polyspace raises a violation.

To resolve this defect, modify foo() to accept a unique_ptr. If modifying foo() is not feasible, there are two alternative solutions.

  • You might create a temporary std::shared_ptr object and pass it directly to foo(), as shown in func2.

  • You might create a std::unique_ptr object and then convert it to a std::shared_ptr object when passing it to foo(), as shown infunc3.

Check Information

Group: General utilities library
Category: Required, Automated

Version History

Introduced in R2022b