Main Content

AUTOSAR C++14 Rule A12-8-2

User-defined copy and move assignment operators should use user-defined no-throw swap function

Since R2021a

Description

Rule Definition

User-defined copy and move assignment operators should use user-defined no-throw swap function.

Rationale

A naive copy or move assignment operator that is implemented without using a swap function might follow the pattern in this code:

class A{
	//...
	A & operator=(const A & rhs)
	{
		if (this != &rhs) // check for self assignment
		{
			// release resource in lhs
			
			// Allocate resource for modified lhs
			
			
			// Copy or move the resources from rhs to lhs
			
		}

		return *this;
	}
private:
	//resources
	int* mArray;
};
Such naive implementation of the copy or move assignment operator cannot provide strong exception safety because if any of the operations raises an exception, the left operand cannot be reverted back to its original state. The preceding pattern is also inefficient because it requires a check for self-assignment. Code duplication between such a copy or move assignment operator and a copy or move constructor makes the code difficult to maintain.

To resolve these issues, utilize user-defined swap functions that do not raise exceptions. Consider this pattern:

class A{
	//...
	A & operator=(A rhs)
	{
		Swap(*this,rhs);
	}
	friend void Swap(A& lhs, A& rhs) noexcept{
		//...
	}
private:
	//resources
	int* mArray;
	
};
This implementation of the copy or move assignment operator does not attempt allocation or deallocation of memory. Instead, It swaps the resources between the left and right operands by calling a user-defined noexcept function Swap. This Swap function might be implemented by utilizing the std::swap function. The benefits of this pattern are:

  • Strong exception safety: This implementation of the copy or move assignment operator takes a temporary copy of the right operand by using the copy or move constructor and swaps the temporary copy with the left operand. Because the move and swap functions must be noexcept, only the copy operation might raise an exception. If this operator raises an exception, only the temporary copy of the right operand might be invalidated. The state of the right or the left operand remains untouched.

  • Code reuse: In this implementation, the copy or move assignment operator reuses the copy or move constructor. The class-specific swap function can also be reused for implementing other algorithms.

  • Efficiency: By eliminating the check against self-assignment, the operator is more efficient.

To implement a copy or move assignment operator, use user-defined noexcet swap functions.

Polyspace Implementation

Polyspace® flags a copy or move assignment operator if it does not contain at least one call to a user-defined swap function. Polyspace identifies functions that have these signatures as swap functions: void T::swap(T&) or void [N::]swap(T&, T&). The first signature represents a member function of class T that takes one argument. The second signature represents a nonmember or static function in the namespace N that takes two arguments. The name swap can be case-insensitive and prefixed or postfixed by underscores.

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 <utility>
#include <string>
class B
{
  public:
    B& operator=(const B& oth) & { // Noncompliant
      if (this != &oth)
      {
        ptr1 = new std::int32_t(*oth.ptr1);
        ptr2 = new std::int32_t(
          *oth.ptr2); // Exception thrown here results in
        // a memory leak of ptr1
      }

      return *this;
    }
    B& operator=(B&& oth) & noexcept { // Noncompliant
      if (this != &oth)
      {
        ptr1 = std::move(oth.ptr1);
        ptr2 = std::move(oth.ptr2);
        oth.ptr1 = nullptr;
        oth.ptr2 = nullptr;
      }

      return *this;
    }
private:
    std::int32_t* ptr1;
    std::int32_t* ptr2;
};

In this example, the copy and move assignment operator for class B uses a naive implementation instead of a copy-and-swap implementation. The copy and move operator of B is inefficient and does not provide strong exception safety. Polyspace flags these operators as noncompliant.

#include <utility>
#include <string>
class C
{
  public:
    C(const C&) = default;
    C(C&&) = default;

    C& operator=(const C& oth) & {     //Noncompliant
      C tmp(oth);
      std::swap(ptr1, tmp.ptr1);       
      return *this;
    }
    C& operator=(C&& oth) & {          // Noncompliant
      C tmp(std::move(oth));
      std::swap(ptr1, tmp.ptr1);       
      return *this;
    }

  private:
    std::int32_t* ptr1;
};

In this example, the copy and move assignment operator for class C uses a copy-and-swap implementation, but uses the standard std::swap function instead of a class-specific, user-defined swap function. Because class C requires user-defined copy and move operator, it also require a user-defined swap function. Polyspace flags the operators as noncompliant.

#include <utility>
#include <string>

class D
{
  public:
    D(const D&) = default;
    D(D&&) = default;
    D& operator=(const D& oth) & {     // Noncompliant
      D tmp(oth);
      _swap_(*this,tmp);
      return *this;
    }
    D& operator=(D&& oth) & {          // Noncompliant
      D tmp(std::move(oth));
      _swap_(*this,tmp);
      return *this;
    }
	//...
	friend void _swap_(D& lhs, D& rhs){ // swap function not noexcept
	//...
	}
};

In this example, the copy and move assignment operator for class D uses a swap function that is not noexcept. These operators do not provide strong exception safety. Polyspace flags them as noncompliant.

#include <utility>
#include <string>

class E
{
  public:
    E(const E&) = default;
    E(E&&) = default;

    E& operator=(const E& oth) & {     // Noncompliant
      E tmp(oth);
      swap(*this,tmp);
      return *this;
    }
    E& operator=(E&& oth) & {          // Noncompliant
      E tmp(std::move(oth));
      swap(*this,tmp);
      return *this;
    }

    // Member function swap
    void swap(E& lhs, E& rhs) noexcept {
      std::swap(lhs.ptr1, rhs.ptr1);
      std::swap(lhs.ptr2, rhs.ptr2);
    }

  private:
    std::int32_t* ptr1;
    std::int32_t* ptr2;
};

In this example, the copy and move assignment operator for class E uses a swap function that takes two arguments. Because the swap function is defined as a nonstatic member function of E, Polyspace expects the E::swap function to have only one argument. Polyspace flags the copy and move operators of E because the swap function has an unexpected signature.

Check Information

Group: Special member functions
Category: Advisory, Automated

Version History

Introduced in R2021a