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.

Código:
#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.

Questões:Eu sei que a maioria dos algoritmos STL passa seu argumento por valor. No entanto, comparado comfunc, 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.8

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)

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.

questionAnswers(2)

yourAnswerToTheQuestion