Expensive copy in a range-based for loop iteration

Переменная цикла основанной на диапазоне for цикл копируется из элементов области значений вместо ссылки, что приводит к неэффективному коду

Описание

Этот дефект возникает, когда переменный цикл основанная на диапазоне for цикл копируется из элементов области значений вместо чтения элементов области значений по ссылке. Скопируйте элементы области значений только тогда, когда это необходимо, поскольку их копирование может привести к неэффективному коду. Этот дефект возникает, когда переменная цикла не изменена, и любое из этих условий верно:

  • Скопированная переменная цикла является большой тривиально копируемой переменной типа. Копирование тривиально копируемого объекта дороже, чем ссылка на него, когда объект велик.

  • Скопированная переменная цикла является нетривиально копируемым типом. Копирование такого объекта может потребовать вызова внешней функции, что дороже, чем ссылки на него. Чтобы проверить, является ли объект нетривиально копируемым, используйте функцию std::is_trivially_copyable. Для получения дополнительной информации об этой функции см. std::is_trivially_copyable в ссылке C++.

Риск

Диапазонные for циклы могут стать неэффективными, когда в каждой итерации цикла выполняется дорогая копия переменной цикла. Рассмотрим этот код:

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

Переменная цикла property объявлен как const вместо const&. В каждой итерации for цикл, std::pair объект копируется с карты property_maps в переменную цикла property. Из-за пропавшего & в объявлении propertоперация дорогостоящего копирования выполняется в каждой итерации вместо операции ссылки, что приводит к неэффективному коду. Потому что этот код компилируется и функционирует правильно, неэффективно for циклы могут не быть замечены. Для аналогичного источника неэффективности смотрите Expensive pass by value и Expensive return by value.

Зафиксировать

Чтобы исправить этот дефект, объявите переменную цикла основанной на диапазоне for цикл как const&. Рассмотрим этот код:

void foo( std::map<std::string, std::string> const& property_map )
{
    for( std::pair< const std::string, std::string > const& property: property_map) 
    {}
}
Потому что переменная цикла property объявлен как const&переменная ссылается на другой элемент карты property_map в каждой итерации цикла, без копирования какого-либо ресурса. Предотвращая дорогостоящую копию в каждой итерации, код становится более эффективным.

Эффективность улучшения могут варьироваться в зависимости от используемого компилятора, реализации библиотеки и окружения.

Примеры

расширить все

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

В этом примере основанные на диапазоне for показаны циклы, которые имеют различные типы переменных цикла.

  • Polyspace® помечает нетривиально копируемую переменную цикла ntt в foo() из-за дорогостоящей операции копирования, которая не требуется, поскольку переменная цикла не изменяется. По этой же причине переменные цикла в foo_auto() и foo_c_array() помечены.

  • Polyspace помечает переменную большого цикла ltt в foo_large() потому что копировать элементы ltts дороже в ltt чем к ссылке элементам lttsхотя ltt является тривиально копируемым типом.

  • Polyspace не помечает переменную цикла stt в foo_small() из-за копирования элементов stts в stt не дороже, чем ссылки на элементы stts.

  • Polyspace не помечает переменную цикла ntt в modify_elem() поскольку переменная цикла изменена в цикле.

Коррекция

Чтобы исправить эту проблему, используйте постоянные ссылки (const&) как цикл переменные в основанных на диапазоне for циклы. Использование const& переменные цикла препятствуют дорогостоящему копированию и производят эффективный код.

#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 ) 
	{}
}

Информация о результатах

Группа: Эффективность
Язык: C++
По умолчанию: Off
Синтаксис командной строки : EXPENSIVE_RANGE_BASED_FOR_LOOP_ITERATION
Влияние: Средний
Введенный в R2020b