メインコンテンツ

AUTOSAR C++14 Rule A12-8-2

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

R2021a 以降

説明

ルール定義

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

根拠

スワップ関数を使用せずに実装されたネイティブなコピー代入演算子または移動代入演算子は、次のコード内のパターンに従っている可能性があります。

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;
};
このようなコピー代入演算子または移動代入演算子のネイティブな実装では、強力な例外安全性を提供することはできません。これは、演算のいずれかで例外が発生した場合に、左オペランドを元の状態に戻すことができないためです。前述のパターンは、自己代入のチェックが必要なことから非効率でもあります。このようなコピー代入演算子または移動代入演算子と、コピー コンストラクターまたは移動コンストラクター間のコード重複は、コードの保守を困難にします。

これらの問題を解決するには、例外を発生させないユーザー定義の関数 swap を使用してください。次のパターンについて考えます。

class A{
	//...
	A & operator=(A rhs)
	{
		Swap(*this,rhs);
	}
	friend void Swap(A& lhs, A& rhs) noexcept{
		//...
	}
private:
	//resources
	int* mArray;
	
};
このコピー代入演算子または移動代入演算子の実装は、メモリの割り当てまたは割り当て解除を試みません。代わりに、ユーザー定義の noexcept 関数 Swap を呼び出すことによって、左オペランドと右オペランドのリソースを入れ替えます。この関数 Swap は、関数 std::swap を利用して実装される場合があります。このパターンのメリットを以下に示します。

  • 強力な例外安全性:このコピー代入演算子または移動代入演算子の実装は、コピー コンストラクターまたは移動コンストラクターを使用して右オペランドの一時コピーを作成し、その一時コピーと左オペランドを入れ替えます。移動関数とスワップ関数は noexcept にする必要があるため、例外が発生する可能性があるのはコピー演算だけです。この演算子で例外が発生した場合に、無効になる可能性があるのは右オペランドの一時コピーだけです。右オペランドまたは左オペランドの状態は変化しません。

  • コードの再利用:この実装では、コピー代入演算子または移動代入演算子でコピー コンストラクターまたは移動コンストラクターが再利用されます。他のアルゴリズムを実装するために、クラス固有の関数 swap を再利用することもできます。

  • 効率性:自己代入に対するチェックを排除することによって、演算子の効率が高まります。

コピー代入演算子または移動代入演算子を実装するには、ユーザー定義の noexcet スワップ関数を使用してください。

Polyspace 実装

Polyspace® は、少なくとも 1 つのユーザー定義のスワップ関数に対する呼び出しを含まないコピー代入演算子または移動代入演算子にフラグを設定します。また、Polyspace は、シグネチャが void T::swap(T&) または void [N::]swap(T&, T&) である関数をスワップ関数として識別します。最初のシグネチャは、1 つの引数を取るクラス T のメンバー関数を表します。2 番目のシグネチャは、2 つの引数を取る名前空間 N 内の非メンバー関数または静的関数を表します。swap という名前の大文字と小文字は区別されず、先頭または末尾にアンダースコアを付加できます。

トラブルシューティング

ルール違反が想定されるものの、Polyspace から報告されない場合は、コーディング規約違反が想定どおりに表示されない理由の診断を参照してください。

すべて展開する

#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;
};

この例では、クラス B のコピー代入演算子と移動代入演算子で、copy-and-swap 実装の代わりにネイティブ実装が使用されます。B のコピー演算子と移動演算子は非効率で、強力な例外安全性を提供しません。Polyspace は、非準拠としてこれらの演算子にフラグを設定します。

#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;
};

この例では、クラス C のコピー代入演算子と移動代入演算子で copy-and-swap 実装が使用されますが、クラス固有のユーザー定義の関数 swap の代わりに標準の関数 std::swap が使用されます。クラス C にはユーザー定義のコピー演算子と移動演算子が必要なため、ユーザー定義の関数 swap も必要です。Polyspace は、非準拠としてこれらの演算子にフラグを設定します。

#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
	//...
	}
};

この例では、クラス D のコピー代入演算子と移動代入演算子で、noexcept ではない関数 swap が使用されます。これらの演算子は強力な例外安全性を提供しません。Polyspace は、非準拠としてこれらの演算子にフラグを設定します。

#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;
};

この例では、クラス E のコピー代入演算子と移動代入演算子で、2 つの引数を取る関数 swap が使用されます。スワップ関数は E の非静的メンバー関数として定義されるため、Polyspace は、関数 E::swap が引数を 1 つしか取らないと想定します。Polyspace は、スワップ関数に予期しないシグネチャが含まれているとして、E のコピー演算子と移動演算子にフラグを設定します。

チェック情報

グループ: 特殊なメンバー関数
カテゴリ: Advisory、Automated

バージョン履歴

R2021a で導入