elimine cópias desnecessárias ao chamar algoritmos C ++ / STL
Eu codifiquei o exemplo a seguir para ilustrar melhor minhas perguntas.
No código abaixo, apresento umobjeto de função (ou seja,funObj
)
NofunObj
definição da classe, uma variável integrante chamadaid
está definido para conter o ID de cadafunObj
construído e uma variável de membro integral estátican
contar ofunObj
objetos criados.
Assim, toda vez que um objetofunObj
É construídon
é aumentado em um e seu valor é atribuído aoid
campo do recém-criadofunObj
.
Além disso, eu defini um construtor padrão, um construtor de cópia e um destruidor. Todos os três estão imprimindo mensagens para ostdout
para significar sua invocação junto com o ID dofunObj
eles estão se referindo.
Eu também defini uma funçãofunc
que recebe como entradas por objetos de valor do tipofunObj
.
#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>
template<typename T>
class funObj {
std::size_t id;
static std::size_t n;
public:
funObj() : id(++n)
{
std::cout << " Constructed via the default constructor, object foo with ID(" << id << ")" << std::endl;
}
funObj(funObj const &other) : id(++n)
{
std::cout << " Constructed via the copy constructor, object foo with ID(" << id << ")" << std::endl;
}
~funObj()
{
std::cout << " Destroyed object foo with ID(" << id << ")" << std::endl;
}
void operator()(T &elem)
{
}
T operator()()
{
return 1;
}
};
template<typename T>
void func(funObj<T> obj) { obj(); }
template<typename T>
std::size_t funObj<T>::n = 0;
int main()
{
std::vector<int> v{ 1, 2, 3, 4, 5, };
std::cout << "> Calling `func`..." << std::endl;
func(funObj<int>());
std::cout << "> Calling `for_each`..." << std::endl;
std::for_each(std::begin(v), std::end(v), funObj<int>());
std::cout << "> Calling `generate`..." << std::endl;
std::generate(std::begin(v), std::end(v), funObj<int>());
// std::ref
std::cout << "> Using `std::ref`..." << std::endl;
auto fobj1 = funObj<int>();
std::cout << "> Calling `for_each` with `ref`..." << std::endl;
std::for_each(std::begin(v), std::end(v), std::ref(fobj1));
std::cout << "> Calling `generate` with `ref`..." << std::endl;
std::for_each(std::begin(v), std::end(v), std::ref(fobj1));
return 0;
}
Resultado:Chamandofunc
...
Construído através do construtor padrão, objeto foo com ID (1)
Objeto destruído foo com ID (1)
Chamandofor_each
...
Construído através do construtor padrão, objeto foo com ID (2)
Construído através do construtor de cópias, objeto foo com ID (3)
Objeto destruído foo com ID (2)
Objeto destruído foo com ID (3)
Chamandogenerate
...
Construído através do construtor padrão, objeto foo com ID (4)
Construído através do construtor de cópias, objeto foo com ID (5)
Objeto destruído foo com ID (5)
Objeto destruído foo com ID (4)
Usandostd::ref
...
Construído através do construtor padrão, objeto foo com ID (6)
Chamandofor_each
comref
...
Chamandogenerate
comref
...
Objeto destruído foo com ID (6)
Discussão:Como você pode ver na saída acima, chamar a funçãofunc
com um objeto temporário do tipofunObj
resulta na construção de um únicofunObj
objeto (mesmo quefunc
passa seu argumento por valor). No entanto, esse parece não ser o caso ao passar objetos temporários do tipofunObj
algoritmos STLstd::for_each
estd::generate
. Nos primeiros casos, o construtor de cópias é evocado e um extrafunObj
É construído. Em algumas aplicações, a criação de cópias "desnecessárias" deteriora significativamente o desempenho do algoritmo. Com base nesse fato, as seguintes perguntas estão sendo levantadas.
func
, que também passa seu argumento de entrada por valor, os algoritmos STL geram uma cópia extra. Qual o motivo dessa cópia "desnecessária"?Existe uma maneira de eliminar essas cópias "desnecessárias"?Ao ligarstd::for_each(std::begin(v), std::end(v), funObj<int>())
efunc(funObj<int>())
em que escopo faz objeto temporáriofunObj<int>
vidas, para cada caso, respectivamente?Eu tentei usarstd::ref
para forçar a passagem por referência e como você pode ver, a cópia "desnecessária" foi eliminada. No entanto, quando tento passar um objeto temporário parastd::ref
(ou seja,std::ref(funObj<int>())
) Eu recebo um erro do compilador. Por que esse tipo de declaração é ilegal?A saída foi gerada usando o VC ++ 2013. Como você pode ver, há uma anomalia ao chamarstd::for_each
os destruidores dos objetos estão sendo chamados em ordem inversa. Por que?Quando executo o código emColiru que executa o GCC v4.8, a anomalia com destruidores é corrigidastd::generate
não gera uma cópia extra. Por que?Detalhes / Comentários:A saída acima foi gerada a partir do VC ++ 2013.Atualizar:Eu também adicionei afunObj
classifique um construtor de movimentação (veja o código abaixo). funObj(funObj&& other) : id(other.id)
{
other.id = 0;
std::cout << " Constructed via the move constructor, object foo with ID(" << id << ")" << std::endl;
}
Também ativei a otimização completa no VC ++ 2013 e compilei no modo de lançamento.Saída (VC ++ 2013):Chamandofunc
...
Construído através do construtor padrão, objeto foo com ID (1)
Objeto destruído foo com ID (1)
Chamandofor_each
...
Construído através do construtor padrão, objeto foo com ID (2)
Construído por meio do construtor move, objeto foo com ID (2)
Objeto destruído foo com ID (2)
Objeto destruído foo com ID (0)
Chamandogenerate
...
Construído por meio do construtor padrão, objeto foo com ID (3)
Construído através do construtor de cópias, objeto foo com ID (4)
Objeto destruído foo com ID (4)
Objeto destruído foo com ID (3)
Usandostd::ref
...
Construído por meio do construtor padrão, objeto foo com ID (5)
Chamandofor_each
comref
...
Chamandogenerate
comref
...
Objeto destruído foo com ID (5)
Saída GCC 4.8Chamandofunc
...
Construído através do construtor padrão, objeto foo com ID (1)
Objeto destruído foo com ID (1)
Chamandofor_each
...
Construído através do construtor padrão, objeto foo com ID (2)
Construído por meio do construtor move, objeto foo com ID (2)
Objeto destruído foo com ID (2)
Objeto destruído foo com ID (0)
Chamandogenerate
...
Construído por meio do construtor padrão, objeto foo com ID (3)
Objeto destruído foo com ID (3)
Construído através do construtor padrão, objeto foo com ID (4)
Chamandofor_each
comref
...
Chamandogenerate
comref
...
Objeto destruído foo com ID (4)
Parece que o VC ++ 2013std::generate
gera uma cópia extra, não importa se os sinalizadores de otimização estão ativados e a compilação está no modo de liberação e além do fato de que um construtor de movimentação está definido.