Main Content

Expensive copy in a range-based for loop iteration

The loop variable of a range-based for loop is copied from the range elements instead of being referenced resulting in inefficient code

Since R2020b

Description

This defect occurs when the loop variable of a range-based for loop is copied from the range elements instead of reading the range elements by reference. Copy the range elements only when its necessary because copying them might result in inefficient code. This defect is raised when the loop variable is unmodified and any of these conditions are true:

  • The copied loop variable is a large trivially copyable type variable. Copying a trivially copyable object is more expensive than referencing it when the object is large.

  • The copied loop variable is a nontrivially copyable type. Copying such an object might require an external function call, which is more expensive than referencing it. To check whether an object is nontrivially copyable, use the function std::is_trivially_copyable. For more details about this function, see std::is_trivially_copyable in the C++ reference.

Risk

Range-based for loops can become inefficient when an expensive copy of the loop variable is made in each iteration of the loop. Consider this code:

void foo( std::map<std::string, std::string> const& property_map )
{
    for( std::pair< const std::string, std::string > const property: property_map) 
    {}
}

The loop variable property is declared as a const instead of const&. In each iteration of the for loop, an std::pair object is copied from the map property_maps to the loop variable property. Because of the missing & in the declaration of propert, an expensive copy operation is done in each iteration instead of a referencing operation, resulting in inefficient code. Because this code compiles and functions correctly, the inefficient for loops might not be noticed. For similar source of inefficiencies, see Expensive pass by value and Expensive return by value.

Fix

To fix this defect, declare the loop variable of a range-based for loop as a const&. Consider this code:

void foo( std::map<std::string, std::string> const& property_map )
{
    for( std::pair< const std::string, std::string > const& property: property_map) 
    {}
}
Because the loop variable property is declared as a const&, the variable references a different element of the map property_map in each loop iteration, without copying any resource. By preventing an expensive copy in each iteration, the code becomes more efficient.

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

Examples

expand all

#include <initializer_list>
#include <unordered_map>
#include <vector>
struct Small_Trivial_Type
{
	unsigned char values[ sizeof( void* ) ];
};

struct Large_Trivial_Type
{
	unsigned char values[ 4u * sizeof( void* ) ];
};

class Nontrivial_Type
{
	Nontrivial_Type() noexcept;
	Nontrivial_Type( Nontrivial_Type const& );
	Nontrivial_Type& operator=( Nontrivial_Type const& );
	~Nontrivial_Type() noexcept;
	int read() const;
	void modify( int );
};
extern std::vector< Nontrivial_Type > getNtts();

void foo( std::vector< Nontrivial_Type > const& ntts )
{
	for( Nontrivial_Type ntt: ntts )
	{}
}

void foo_auto( std::vector< Nontrivial_Type > const& ntts )
{
	for( auto ntt: ntts ) 
	{}
}
void foo_c_array( Nontrivial_Type const ( & ntts )[ 10 ] )
{
	for( Nontrivial_Type ntt: ntts )
	{}
}
void foo_large( std::vector< Large_Trivial_Type > const& ltts )
{
    for( Large_Trivial_Type ltt: ltts ) 
    {}
}
void foo_small( std::vector< Small_Trivial_Type > const& stts )
{
    for( Small_Trivial_Type const stt: stts ) 
    {}
}
void modify_elem( std::vector< Nontrivial_Type > const& ntts )
{
	for( Nontrivial_Type ntt: ntts ) 
	{
		ntt.modify( 42 );//Modification
	}
}

In this example, range-based for loops that have different types of loop variables are shown.

  • Polyspace® flags the nontrivially copyable loop variable ntt in foo() due to an expensive copy operation that is unnecessary because the loop variable is not modified. For the same reason, the loop variables in foo_auto() and foo_c_array() are flagged.

  • Polyspace flags the large loop variable ltt in foo_large() because it is more expensive to copy the elements of ltts into ltt than to reference elements of ltts, even though ltt is a trivially copyable type.

  • Polyspace does not flag the loop variable stt in foo_small() because copying the elements of stts into stt is not more expensive than referencing the elements of stts.

  • Polyspace does not flag the loop variable ntt in modify_elem() because the loop variable is modified in the loop.

Correction

To fix this issue, use constant references (const&) as loop variables in range-based for loops. Using const& loop variables prevents expensive copying and produces efficient code.

#include <initializer_list>
#include <unordered_map>
#include <vector>
struct Small_Trivial_Type
{
	unsigned char values[ sizeof( void* ) ];
};

struct Large_Trivial_Type
{
	unsigned char values[ 4u * sizeof( void* ) ];
};

class Nontrivial_Type
{
	Nontrivial_Type() noexcept;
	Nontrivial_Type( Nontrivial_Type const& );
	Nontrivial_Type& operator=( Nontrivial_Type const& );
	~Nontrivial_Type() noexcept;
	int read() const;
	void modify( int );
};
extern std::vector< Nontrivial_Type > getNtts();
// Test iterating over a const vector.
void foo( std::vector< Nontrivial_Type > const& ntts )
{
	for( Nontrivial_Type const& ntt: ntts ) // NC2C
	{}
}

void foo_auto( std::vector< Nontrivial_Type > const& ntts )
{
	for( auto const& ntt: ntts ) //NC2C
	{}
}
void foo_c_array( Nontrivial_Type const ( & ntts )[ 10 ] )
{
	for( Nontrivial_Type const& ntt: ntts ) // NC2C
	{}
}
void foo_large( std::vector< Large_Trivial_Type > const& ltts )
{
	for( Large_Trivial_Type const& ltt: ltts ) 
	{}
}

Result Information

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

Version History

Introduced in R2020b