Main Content

MISRA C++:2023 Rule 28.6.3

An object shall not be used while in a potentially moved-from state

Since R2024b

Description

Rule Definition

An object shall not be used while in a potentially moved-from state.

Rationale

If you convert an lvalue reference to rvalue reference by using std::move() or std::forward, the original lvalue reference remains in a potentially moved-from state, which can be an indeterminate state. In this code, the object original is passed as an lvalue reference to foo(), which converts the lvalue reference to an rvalue reference:

class myobj {/**/
};

void foo(myobj &A) {
	myobj B{std::move(A)}
}

void bar() {
	myobj original;
	foo(original)
}
After foo() is called the object original is in an indeterminate state. Using the original object can have unexpected results.

Avoid using objects while they are in a potentially moved-from state. If your function accepts a lvalue reference, avoid keeping them in a move-from state when the function returns.

This rule does not apply when:

  • You use a std::unique_ptr. The mover-from state of std::unique_ptr is well-defined and equal to nullptr.

  • You assign a value to the potentially moved-from object.

  • You destroy the potentially moved-from object.

Polyspace Implementation

The rule checker reports a violation of this rule if any of these conditions are true:

  • An object is read after its contents are moved to a destination object by calling the std::move function explicitly.

  • An object forwarded by explicitly calling std::forward is reused after the call. Polyspace® does not flag the call to std::forward if its argument is reused in a branch that cannot be reached after the call to std::forward. For example, in this code snippet, the branch where the reuse of variable t occurs cannot be reached after the execution enters the branch where std::forward is used.

    template<typename T>
    void func(T&& t)
    {
      T&& p = t;
    
      switch(t) { 
        case 0:
          p = std::forward<T>(t); 
          break;
        case 1:
          t--; //t reused 
          break;
      }
    }
    

  • An lvalue reference parameter of a function is moved-from in the function body.

Polyspace does not flag accessing a source object after its contents are moved if any of these conditions are true:

  • The source object of an explicit move operation is one of these types:

    • std::unique_ptr

    • std::shared_ptr

    • std::weak_ptr

    • std::basic_ios

    • std::basic_filebuf

    • std::thread

    • std::unique_lock

    • std::shared_lock

    • std::promise

    • std::future

    • std::shared_future

    • std::packaged_task

  • The move operation is performed implicitly. For example, the function std::remove moves objects implicitly. Polyspace does not flag accessing the object moved implicitly. To avoid accidentally accessing a moved object, erase the removed object by using std::erase. For details about using std::remove, see Improper erase-remove idiom.

  • The source object is of a built-in base type, such as: int, enum, float, double, std::intptr_t, std::nullptr_t, and pointer types.

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 how this rule checker reports violations when source objects are read after an explicit move operation.

#include<string>
#include<iostream>
void F1()
{
	std::string s1{"string"};
	std::string s2{std::move(s1)}; 
	// ...
	std::cout
	<<  // Noncompliant
	s1
	<< "\n";
}

void F2()
{
	std::unique_ptr<std::int32_t> ptr1 = std::make_unique<std::int32_t>(0);
	std::unique_ptr<std::int32_t> ptr2{std::move(ptr1)};
	std::cout << ptr1.get() << std::endl; // Compliant by exception
}
void g(std::string v)
{
	std::cout << v << std::endl; 
}

void F3()
{
	std::string s;
	for (unsigned i = 0; i < 10; ++i) {
		s.append(1, static_cast<char>('0' + i));  //Noncompliant 
		g(std::move(s));
	}
}
void F4()
{
	for (unsigned i = 0; i < 10; ++i) {
		std::string s(1, static_cast<char>('0' + i)); // Compliant
		g(std::move(s));  
	}
}

  • The function F1() explicitly moves string s1 to s2 by calling std::move. After the move operation, the function attempts to read s1. Polyspace flags this attempt of reading a source object after an explicit move.

  • The function F2() explicitly moves the unique pointer ptr1 to ptr2. Because the std::unique_ptr remains in a well-defined state after the move, reading a source unique pointer after an explicit move is compliant with this rule.

  • The function F3() explicitly moves the string s and then attempts to read s by calling the std::string::append function. Polyspace flags this attempt of reading a source object after an explicit move.

  • The function F4() explicitly moves the string s. In each iteration of the loop, s is assigned a specific value before each move operation. As a result, the state of s is specified before the object is accessed. This method of accessing the source object after a move operation is compliant with this rule.

In this example, this rule checker reports violations for calls to std::forward where the forwarded parameter is reused after the call. In template function f4(), the second call to std::forward counts as a reuse of the parameter t4. There are no violations of this rule in the template function f2() because t2 is reused in the else branch which is never entered.

#include <cstdint>
#include <iostream>
#include <utility>

namespace myTemplates
{

template <typename ...T>
void f1(T...t);

template<typename T>
void f2(T&& t2, bool b)
{
    if (b) {
        f1(std::forward<T>(t2)); // Compliant
    } else {
        t2++;  // else branch not entered
    }

}


template<typename T>
void f3(T&& t3)
{
    T&& p = std::forward<T>(t3); // Noncompliant

    switch (t3) {                // t3 reused
    case 0:
        t3++;                    // t3 reused
        break;
    case 1:
        t3--;                    // t3 reused
        break;
    default:
        break;
    }
}


template<typename T>
void f4(T&& t4)
{
    --t4;

    f1(std::forward<T>(t4)); // Noncompliant

    t4++;                    // t4 reused

    f1(std::forward<T>(t4)); // Noncompliant and t4 reused

    t4--;                    // t4 reused
}


template<typename T>
void f5(T&& t5)
{
    f1(t5,                   // t5 reused
       std::forward<T>(t5)); // Noncompliant
}

}

void main(void)
{
    int i;

    myTemplates::f2(i, true);
    myTemplates::f3(i);
    myTemplates::f4(i);
    myTemplates::f5(i);

}

In this example, the rule checker reports a violation when a function moves an lvalue reference parameter and keeps the parameter in a moved-from state when it returns.

#include <utility>
#include <string>
struct myobj {
	int a = 0;
	std::string str = "string";
};

void foo(myobj &A) {
	myobj B(std::move(A)); //Noncompliant
}

void bar() {
	myobj original;
	foo(original);
	original.a++;
}

Check Information

Group: Algorithms Library
Category: Required

Version History

Introduced in R2024b