Vermeiden Sie unnötige Kopien beim Aufrufen von C ++ / STL-Algorithmen

Ich habe das folgende Beispiel codiert, um meine Fragen besser zu veranschaulichen.

Im folgenden Code stelle ich einFunktionsobjekt (d. h.funObj).

ImfunObj Klassendefinition eine integrale Elementvariable mit dem Namenid ist definiert, um die ID von jedem zu haltenfunObj konstruiert und eine statische Integralelementvariablen um die zu zählenfunObj Objekte erstellt.

Somit jedes Mal ein ObjektfunObj ist konstruiertn wird um eins erhöht und sein Wert wird dem zugewiesenid Feld der neu erstelltenfunObj.

Außerdem habe ich einen Standardkonstruktor, einen Kopierkonstruktor und einen Destruktor definiert. Alle drei drucken Nachrichten an diestdout um ihren Aufruf zusammen mit der ID der zu kennzeichnenfunObj sie beziehen sich auf.

Ich habe auch eine Funktion definiertfunc das nimmt als Eingaben von Wertobjekten des TypsfunObj.

Code:
#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;
}
Ausgabe:

Berufungfunc...

Konstruiert über den Standardkonstruktor, Objekt foo mit ID (1)

Zerstörtes Objekt foo mit ID (1)

Berufungfor_each...

Konstruiert über den Standardkonstruktor, Objekt foo mit ID (2)

Konstruiert über den Kopierkonstruktor, Objekt foo mit ID (3)

Zerstörtes Objekt foo mit ID (2)

Zerstörtes Objekt foo mit ID (3)

Berufunggenerate...

Konstruiert über den Standardkonstruktor, Objekt foo mit ID (4)

Konstruiert über den Kopierkonstruktor, Objekt foo mit ID (5)

Zerstörtes Objekt foo mit ID (5)

Zerstörtes Objekt foo mit ID (4)

Verwendenstd::ref...

Konstruiert über den Standardkonstruktor, Objekt foo mit ID (6)

Berufungfor_each mitref...

Berufunggenerate mitref...

Zerstörtes Objekt foo mit ID (6)

Diskussion:

Wie Sie der obigen Ausgabe entnehmen können, wird die Funktion aufgerufenfunc mit einem temporären Objekt vom TypfunObj ergibt die Konstruktion eines einzigenfunObj Objekt (obwohlfunc übergibt sein Argument als Wert). Dies scheint jedoch nicht der Fall zu sein, wenn temporäre Objekte vom Typ übergeben werdenfunObj zu STL-Algorithmenstd::for_each undstd::generate. In den ersteren Fällen wird der Kopierkonstruktor aufgerufen und ein ExtrafunObj ist konstruiert. In einigen Anwendungen verschlechtert die Erstellung solcher "unnötigen" Kopien die Leistung des Algorithmus erheblich. Basierend auf dieser Tatsache werden die folgenden Fragen aufgeworfen.

Fragen:Ich weiß, dass die meisten STL-Algorithmen ihr Argument als Wert übergeben. Im Vergleich zufuncDie STL-Algorithmen generieren eine zusätzliche Kopie, die auch das Eingabeargument als Wert übergibt. Was ist der Grund für diese "unnötige" Kopie?Gibt es eine Möglichkeit, solche "unnötigen" Kopien zu beseitigen?Beim anrufenstd::for_each(std::begin(v), std::end(v), funObj<int>()) undfunc(funObj<int>()) in welchem Umfang tut temporäres ObjektfunObj<int> lebt, für jeden Fall jeweils?Ich habe versucht zu verwendenstd::ref um die Weitergabe als Referenz zu erzwingen und wie Sie sehen, wurde die "unnötige" Kopie entfernt. Wenn ich jedoch versuche, ein temporäres Objekt an zu übergebenstd::ref (d. h.std::ref(funObj<int>())) Ich erhalte einen Compilerfehler. Warum sind solche Aussagen illegal?Die Ausgabe wurde mit VC ++ 2013 generiert. Wie Sie sehen, kommt es beim Anruf zu einer Anomaliestd::for_each Die Destruktoren der Objekte werden in umgekehrter Reihenfolge aufgerufen. Warum ist das so?Wenn ich den Code weiterlaufeColiru das mit gcc v4.8 läuft die anomalie mit destruktoren ist jedoch behobenstd::generate generiert keine zusätzliche Kopie. Warum ist das so?Details / Bemerkungen:Die Ausgabe oben wurde aus VC ++ 2013 generiert.Aktualisieren:Ich habe auch hinzugefügtfunObj klassifizieren Sie einen Verschiebungskonstruktor (siehe Code unten).
 funObj(funObj&& other) : id(other.id)
  {
    other.id = 0;
    std::cout << "    Constructed via the move constructor, object foo with ID(" << id << ")" << std::endl;
  }
Ich habe auch die vollständige Optimierung in VC ++ 2013 aktiviert und im Release-Modus kompiliert.Ausgabe (VC ++ 2013):

Berufungfunc...

Konstruiert über den Standardkonstruktor, Objekt foo mit ID (1)

Zerstörtes Objekt foo mit ID (1)

Berufungfor_each...

Konstruiert über den Standardkonstruktor, Objekt foo mit ID (2)

Konstruiert über den move Konstruktor, Objekt foo mit ID (2)

Zerstörtes Objekt foo mit ID (2)

Zerstörtes Objekt foo mit ID (0)

Berufunggenerate...

Konstruiert über den Standardkonstruktor, Objekt foo mit ID (3)

Konstruiert über den Kopierkonstruktor, Objekt foo mit ID (4)

Zerstörtes Objekt foo mit ID (4)

Zerstörtes Objekt foo mit ID (3)

Verwendenstd::ref...

Konstruiert über den Standardkonstruktor, Objekt foo mit ID (5)

Berufungfor_each mitref...

Berufunggenerate mitref...

Zerstörtes Objekt foo mit ID (5)

Output GCC 4.8

Berufungfunc...

Konstruiert über den Standardkonstruktor, Objekt foo mit ID (1)

Zerstörtes Objekt foo mit ID (1)

Berufungfor_each...

Konstruiert über den Standardkonstruktor, Objekt foo mit ID (2)

Konstruiert über den move Konstruktor, Objekt foo mit ID (2)

Zerstörtes Objekt foo mit ID (2)

Zerstörtes Objekt foo mit ID (0)

Berufunggenerate...

Konstruiert über den Standardkonstruktor, Objekt foo mit ID (3)

Zerstörtes Objekt foo mit ID (3)

Konstruiert über den Standardkonstruktor, Objekt foo mit ID (4)

Berufungfor_each mitref...

Berufunggenerate mitref...

Zerstörtes Objekt foo mit ID (4)

Es scheint, dass VC ++ 2013std::generate Generiert eine zusätzliche Kopie, unabhängig davon, ob Optimierungsflags aktiviert sind und sich die Kompilierung im Freigabemodus befindet und ob außerdem ein Verschiebungskonstruktor definiert ist.

Antworten auf die Frage(2)

Ihre Antwort auf die Frage