Main Content

MISRA C++:2023 Dir 15.8.1

User-provided copy assignment operators and move assignment operators shall handle self-assignment

Since R2024b

Description

Directive Definition

User-provided copy assignment operators and move assignment operators shall handle self-assignment.

Rationale

If a class needs to manage resources, best practice is to use a manager class such as smart pointers or containers from the C++ standard library. If using a manager class is not possible, then the user-provided copy assignment operator and move assignment operator of this class must be able to handle self-assignments. Consider a class myClass that manages the raw pointer data. This class shows a naive implementation of the copy assignment operator for the class:

// Naive Copy Assignment Operator
myClass &operator=(const myClass &other) {
	delete data;
	data = new int(*other.data);

	return *this;
}
For self-assignment operations, this copy assignment operator attempts to read from deleted storage, which results in undefined behavior. If a class has user-provided copy and move operations, unintentional self-assignment operations can be hard to identify or prevent, resulting in performance issues, resource leakage, and undefined behavior.

This directive extends the CopyAssignable and MoveAssignable requirements to classes utilizing user-provided copy and move assignment operators. Avoid naive implementations of copy or move assignment operator. Use established idioms, such as copy-and-swap, when appropriate.

Polyspace Implementation

Polyspace® reports a violation of this rule if user-provided copy and move assignment operators do not check for self-assignment. Polyspace does not report a violation if a copy assignment operator uses the copy-and-swap idiom or if a move assignment operator uses a move-and-swap idiom.

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

This example shows a class template SmartPointer that manages a raw pointer. The user-provided copy and move assignment operators do not check for self-assignment. Polyspace reports violations for these operators.

#include <cstdint>
#include <utility>
#include <memory>

template <typename T>
class SmartPointer {
private:
    T* ptr;

public:
    // Constructor
    explicit SmartPointer(T* p = nullptr) : ptr(p) {}

    // Destructor
    ~SmartPointer() {
        delete ptr;
    }

    // Copy Constructor
    SmartPointer(const SmartPointer& other) {
        ptr = new T(*other.ptr);
    }

    // Move Constructor
    SmartPointer(SmartPointer&& other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr;
    }

    // Naive Copy Assignment Operator
    SmartPointer& operator=(const SmartPointer& other) {//Noncompliant
        
            delete ptr;
            ptr = new T(*other.ptr);
        
        return *this;
    }

    // Naive Move Assignment Operator
    SmartPointer& operator=(SmartPointer&& other) noexcept { //Noncompliant
        
            delete ptr;
            ptr = other.ptr;
            other.ptr = nullptr;
        
        return *this;
    }

    // Dereference operator
    T& operator*() const {
        return *ptr;
    }

    // Member access operator
    T* operator->() const {
        return ptr;
    }
};

void foo() {
    SmartPointer<int> sp1(new int(10));
    SmartPointer<int> sp2(new int(20));

    sp2 = sp1; // Copy assignment
    
    
    SmartPointer<int> sp3(new int(30));
    sp3 = std::move(sp1); // Move assignment
   

}
Correction — Check for Self-Assignment

To resolve this violation, check for self-assignment in the copy and move assignment operators.

#include <iostream>

template <typename T>
class SmartPointer {
private:
	T *ptr;

public:
	// Constructor
	explicit SmartPointer(T *p = nullptr) : ptr(p) {}

	// Destructor
	~SmartPointer() {
		delete ptr;
	}

	// Copy Constructor
	SmartPointer(const SmartPointer &other) {
		ptr = new T(*other.ptr);
	}

	// Move Constructor
	SmartPointer(SmartPointer &&other) noexcept : ptr(other.ptr) {
		other.ptr = nullptr;
	}

	// Copy Assignment Operator
	SmartPointer &operator=(const SmartPointer &other) {
		if(this != &other) {  // Check for self-assignment - Compliant
			delete ptr;
			ptr = new T(*other.ptr);
		}
		return *this;
	}

	// Move Assignment Operator
	SmartPointer &operator=(SmartPointer &&other) noexcept {
		if(this != &other) {  // Check for self-assignment - Compliant
			delete ptr;
			ptr = other.ptr;
			other.ptr = nullptr;
		}
		return *this;
	}

	// Dereference operator
	T &operator*() const {
		return *ptr;
	}

	// Member access operator
	T *operator->() const {
		return ptr;
	}
};

int main() {
	SmartPointer<int> sp1(new int(10));
	SmartPointer<int> sp2(new int(20));

	sp2 = sp1; // Copy assignment
	std::cout << *sp2 << std::endl; // Output: 10

	SmartPointer<int> sp3(new int(30));
	sp3 = std::move(sp1); // Move assignment
	std::cout << *sp3 << std::endl; // Output: 10

	return 0;
}

Check Information

Group: Special member functions
Category: Required

Version History

Introduced in R2024b