Main Content

Move operation uses copy

Non-const rvalue reference parameter of a function or operator is copied instead of moved

Since R2021b

Description

Consider a callable entity that accepts a non-const rvalue reference (X&&) parameter, called a consume parameter. Polyspace® reports a defect if the callable entity copies the parameter instead of using std::move(). In particular, Polyspace reports a defect in each of these scenarios:

  • A move constructor or move assignment operator of a class copies data member or base class. This can happen if the move operator or move assignment operator omits calls to std::move(). For example, the move constructor of the wrapper class copies the data member data.

    class wrapper{
    	//...
    	wrapper(wrapper&& input): data(input.data){ //Missing call to std::move()
    		//...
    	}
    
    	private:
    std::string data;
    }
    Polyspace does not report a defect if move operations are unavailable for the copied data member or base class and if the data member is cheap to copy. Polyspace considers an object cheap to copy if the size of the object is less than twice the size of a pointer.

  • A consume parameter of a function is not completely moved by using std::move() in the function body. This can happen if the function body omits a call to std::move() or if the consume parameter is only partially moved. For example, in this code, the function func() accepts a vector as a consume parameter. In the body of the function, v is copied instead of moved.

    void func(vector<int> &&v)  // func() consumes v
    {
    	// comsume the vector
    	process(v);// Defect- v is not consumed
    
    }
    As an exception, this defect is not reported if move constructors and move assignment operators move their arguments only partially.

Risk

Consume parameters are intended to be moved using std::move(). Copying these objects results in inefficient code. Because the inefficient code compiles and runs correctly, the omission of std::move() can be difficult to detect.

Fix

To fix this defect, use std::move() to move consume parameters, including arguments of move constructors or move assignment operators. When implementing move semantics in a class, best practice is to use the default implicit move constructors and move assignment operators by setting them as = default. Instead of managing raw resources, use containers from the Standard Template Library.

Performance improvements might vary based on the compiler, library implementation, and environment that you are using.

Examples

expand all

In this example, the move constructor and move assignment operator of the class UtilInterface omit calls to std::move(). As a result, the move operations of the class copy the data member. Polyspace reports this defect for the unexpected copy operations.

#include <memory>
#include <string>
#include <utility> 

class ManagerInterface;
// ...
class UtilInterface
{
public:
	// ...
	UtilInterface( UtilInterface&& other )
	  : m_name( other.m_name ), 
	    m_manager( other.m_manager ) 
	{/**/}
	UtilInterface& operator=( UtilInterface&& other )
	{
		m_name = other.m_name; 
		m_manager = other.m_manager; 
		return *this;
	}
	// ...
private:
	// ...
	std::string m_name;
	std::shared_ptr< ManagerInterface > m_manager;
	// ...
};
Correction — Use std::move()

To fix the defect, use std::move() in the move constructor and move assignment operator.

#include <memory>
#include <string>
#include <utility> 

class ManagerInterface;
// ...
class UtilInterface
{
public:
	// ...
	UtilInterface( UtilInterface&& other )
	  : m_name( std::move(other.m_name) ), 
	    m_manager( std::move(other.m_manager) ) 
	{/**/}
	UtilInterface& operator=( UtilInterface&& other )
	{
		m_name = std::move(other.m_name); 
		m_manager = std::move(other.m_manager); 
		return *this;
	}
	// ...
private:
	// ...
	std::string m_name;
	std::shared_ptr< ManagerInterface > m_manager;
	// ...
};
Correction — Use Default Move Constructor and Move Assignment Operator

Another method of fixing this defect is to declare the move assignment operator and the move constructor using = default.

#include <memory>
#include <string>
#include <utility> 

class ManagerInterface;
// ...
class UtilInterface
{
public:
	// ...
	UtilInterface( UtilInterface&& other ) = default;  
	UtilInterface& operator=( UtilInterface&& other ) = default;
	// ...
private:
	// ...
	std::string m_name;
	std::shared_ptr< ManagerInterface > m_manager;
	// ...
};

In this example, the data member myStr of the class myClass is set to a string by using move semantics.

  • Polyspace reports a defect on the function myClass::set2 because the body of the function copies the consume parameter str instead of moving it. Similarly, the function myClass::set3 shows two defects on the consume parameters strA and strB because the parameters are not moved in the function body.

  • Polyspace does not report a defect on the function myClass::set1 because the consume parameter str1 is moved in the body of the function.

#include <utility>
#include <string>
class myClass
{
	myClass(myClass &&c): myStr{std::move(c.myStr)}
	{
	}

	myClass &operator=(myClass &&c)
	{
		myStr = std::move(c.myStr);

		return *this;
	}

public:
	std::string myStr;
	void set1(std::string &&str1)     // No defect
	{
		myStr = std::move(str1);
	}

	void set2(std::string &&str);  
	void set3(std::string &&strA,  // Defect
	          std::string &&strB)  // Defect
	{
		if(strA != strB)
		{
		}
	}

};
void myClass::set2(std::string &&str)   // Defect
{
	myStr = str;
}

Correction — Move consume Parameter

To fix these defects, move the consume parameters in each function body. If a parameter does not require moving, do not declare it as a consume parameter. For example:

  • To fix the defect reported for myClass::set2, move the parameter str by using std::move() in the function body.

  • For myClass::set3, the parameters strA and strB do not require moving. Declare these parameters as const references instead of non-const rvalue references.

#include <utility>
#include <string>
class myClass
{
	myClass(myClass &&c): myStr{std::move(c.myStr)}
	{
	}

	myClass &operator=(myClass &&c)
	{
		myStr = std::move(c.myStr);

		return *this;
	}

public:
	std::string myStr;
	void set1(std::string &&str1)     // No defect
	{
		myStr = std::move(str1);
	}

	void set2(std::string &&str);
	void set3(const std::string &strA,  // No Defect
	          const std::string &strB)  // No Defect
	{
		if(strA != strB)
		{
		}
	}

};
void myClass::set2(std::string &&str)   // No Defect
{
	myStr = std::move(str);
}

Result Information

Group: Performance
Language: C++
Default: Off
Command-Line Syntax: MOVE_OPERATION_USES_COPY
Impact: Medium

Version History

Introduced in R2021b

expand all