Vypuštění kopírování

Vypuštění kopírování (anglicky copy elision) je v programovacím jazyce C++ technika optimalizace překladu, která odstraňuje zbytečné kopírování objektů.

Norma jazyka C++ obecně umožňuje implementacím provádět libovolné optimalizace, za předpokladu, že pozorovatelné chování výsledného programu je stejné jako kdyby byl program byl prováděn přesně tak, jak popisuje norma. Kromě toho norma popisuje také několik situací, kdy lze kopírování vypustit, i když to mění chování programu, z nichž nejobvyklejší je optimalizace návratové hodnoty (viz níže). Jinou široce implementovanou optimalizací popsanou v normě C++ je, když se dočasný objekt typu třídy zkopíruje do objektu téhož typu.[1][2] V důsledku toho je efektivita kopírovací inicializace obvykle stejná jako efektivita přímé inicializace, i když mají jinou sémantiku; kopírovací inicializace stále vyžaduje, aby byl dostupný kopírovací konstruktor.[3] Optimalizaci nelze uplatnit na dočasný objekt, které byl svázán s referencí.

Příklad

editovat
#include <iostream>

int n = 0;

struct C {
  explicit C(int) {}
  C(const C&) { ++n; }  // kopírovací konstruktor má viditelný postranní efekt
};                      // modifikuje objekt se statickou dobou trvání

int main() {
  C c1(42);      // přímá inicializace, volá C::C(int)
  C c2 = C(42);  // kopírovací inicializace, volá C::C(const C&)

  std::cout << n << std::endl;  // vypíše 0, pokud kopírování bylo vypuštěno, jinak 1
}

Podle normy může být taková optimalizace uplatněna na objekty výjimek, které byly je vyhozeny a zachyceny,[4][5] ale je nejasné, zda lze optimalizaci provést jak pro kopírování z vyhozeného objekt do výjimkového objektu, tak pro kopírování z výjimkového objektu do objektu deklarovaného v deklaraci výjimky klauzule catch. Také není jasné, zda tato optimalizaci pouze platí pro dočasné objekty nebo také pro pojmenované objekty.[6] Uvažujme následující program:

#include <iostream>

struct C {
  C() = default;
  C(const C&) { std::cout << "Hello World!\n"; }
};

void f() {
  C c;
  throw c;  // kopírování pojmenovaného objektu C do výjimkového objektu.
}  // Není jasné, zda toto kopírování může být vypuštěno.

int main() {
  try {
    f();
  } catch (C c) {  // kopírování výjimkového objektu do dočasného objektu
                   // z deklarace výjimky.
  }  // Také není jasné, zda toto kopírování může být vypuštěno.
}

Vyhovující překladač by tedy měl vytvořit program, který vypíše „Hello World!“ dvakrát. V revizi C++11 normy jazyka C++, byly tyto problémy vysvětleny a v podstatě bylo umožněno vypustit jak kopírování z pojmenovaného objektu do výjimkového objektu, tak kopírování do objektu deklarovaného v obsluze výjimky.[6]

GCC poskytuje volbu -fno-elide-constructors, která zakazuje vypouštění kopírování. Změnou této volby lze sledovat efekty optimalizace návratové hodnoty nebo jiných optimalizací, které vypouštějí kopírování. Obecně se nedoporučuje tuto důležitou optimalizaci zakazovat.

C++17 poskytuje „zaručené vypuštění kopírování“, kdy prvalue není materializována, dokud není potřebná, a pak je zkonstruována přímo do svého konečného umístění.[7]

Optimalizace návratové hodnoty

editovat

V kontextu programovacího jazyka C++ je optimalizace návratové hodnoty (anglicky return value optimization, RVO) je optimalizací překladu, která zahrnuje odstranění dočasného objektu vytvořeného pro udržování návratové hodnoty funkce.[8] Podle normy C++ může RVO změnit pozorovatelné chování výsledného programu.[9]

Shrnutí

editovat

Obecně norma C++ umožňuje, aby překladač prováděl libovolnou optimalizaci, za předpokladu, že výsledný spustitelný soubor vykazuje stejný pozorovatelný chování jako kdyby všechny požadavky normy byly splněny. Toto pravidlo se obvykle nazývá „as-if rule“.[10][2] Termín optimalizace návratové hodnoty odkazuje na zvláštní ustanovení v normě C++, která jde ještě dále než toto pravidlo: implementace může vypustit operaci kopírování způsobenou příkazem return, i když kopírovací konstruktorpostranní efekty.[1][2]

Následující příklad ukazuje scénář, kde implementace může vypustit jedno nebo dvě kopírování, i když kopírovací konstruktor má viditelný postranní efekt (vypsání textu).[1][2] První kopírování, které může být vypuštěno, je kopírování bezejmenného dočasného objektu C do návratové hodnoty funkce f. Druhé kopírování, které může být vypuštěno, je kopírování dočasného objektu vraceného funkcí f do obj.

#include <iostream>

struct C {
  C() = default;
  C(const C&) { std::cout << "Došlo ke kopírování.\n"; }
};

C f() {
  return C();
}

int main() {
  std::cout << "Hello World!\n";
  C obj = f();
}

Podle použitého překladače a jeho nastavení, výsledný program může vypsat libovolný z následujících výstupů:

Hello World!
Došlo ke kopírování.
Došlo ke kopírování.
Hello World!
Došlo ke kopírování.
Hello World!

Pozadí

editovat

Vracení objektu vestavěného typu z funkce má obvykle jen malou režii, protože hodnota se typicky vejde do registru procesoru. Vracení většího objektu typu třídy může vyžadovat nákladnější kopírování z jednoho místa v paměti na jiné. Aby se tomu zabránilo, implementace může vytvořit skrytý objekt v zásobníkovém rámci volající funkce, a volající funkci vrátit pouze adresu tohoto objektu. Návratová hodnota funkce se pak zkopíruje do skrytého objektu.[11] Tedy následující kód:

struct Data { 
  char bytes[16]; 
};

Data F() {
  Data result = {};
  // tvorba výsledku
  return result;
}

int main() {
  Data d = F();
}

může generovat stejný kód jako:

struct Data {
  char bytes[16];
};

Data* F(Data* _hiddenAddress) {
  Data result = {};
  // zkopíruje result do skrytého objektu
  *_hiddenAddress = result;
  return _hiddenAddress;
}

int main() {
  Data _hidden;           // vytvoří skrytý objekt
  Data d = *F(&_hidden);  // zkopíruje výsledek do d
}

což způsobí, že objekt Data bude kopírován dvakrát.

V počátcích vývoje jazyka C++ byla jeho neschopnost efektivně vracet objekt typu třídy z funkce považována za slabou stránku jazyka.[12] Kolem roku 1991 implementoval Walter Bright techniku, která minimalizuje kopírování, efektivně nahrazování skrytý objekt a pojmenovaný objekt v funkce objektem používaným pro uložení výsledku:[13]

struct Data {
  char bytes[16];
};

void F(Data* p) {
  // vytvoří výsledek přímo v *p
}

int main() {
  Data d;
  F(&d);
}

Bright implementoval tuto optimalizaci ve svém překladači Zortech C++.[12] Později byla tato technika pojmenována „optimalizace návratové hodnoty“ (NRVO).[13]

Podpora v překladačích

editovat

Optimalizaci návratové hodnoty podporuje většina překladačů.[8][14][15] V některých případech však překladač nemůže optimalizaci provést. Častým případem je, když funkce může vracet různé pojmenované objekty podle trajektorie provádění:[11][14][16]

#include <string>
std::string F(bool cond = false) {
  std::string first("first");
  std::string second("second");
  // funkce může vrátit jeden ze dvou pojmenovaných objektů
  // podle svého argumentu. RVO nemusí být použito
  return cond ? first : second;
}

int main() {
  std::string result = F();
}

Reference

editovat

V tomto článku byl použit překlad textu z článku Copy elision na anglické Wikipedii.

  1. a b c ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §12.8 Copying class objects [class.copy] para. 15
  2. a b c d ISO/IEC, 2003. ISO/IEC 14882:2003(E): Programming Languages - C++. [s.l.]: [s.n.]. Dostupné v archivu pořízeném z originálu dne 2023-04-10. Kapitola § 12.8 Copying class objects [class.copy]. 
  3. SUTTER, Herb, 2001. More Exceptional C++. [s.l.]: Addison-Wesley. 
  4. ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §15.1 Throwing an exception [except.throw] para. 5
  5. ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §15.3 Handling an exception [except.handle] para. 17
  6. a b C++ Standard Core Language Defect Reports [online]. WG21 [cit. 2009-03-27]. Dostupné online. 
  7. Copy elision [online]. [cit. 2024-09-12]. Dostupné online. 
  8. a b MEYERS, Scott, 1995. More Effective C++. [s.l.]: Addison-Wesley. Dostupné online. ISBN 9780201633719. 
  9. ALEXANDRESCU, Andrei. Move Constructors [online]. 2003-02-01 [cit. 2009-03-25]. Dostupné online. 
  10. ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §1.9 Program execution [intro.execution] para. 1
  11. a b BULKA, Dov; David Mayhew, 2000. Efficient C++. [s.l.]: Addison-Wesley. ISBN 0-201-37950-3. 
  12. a b LIPPMAN, Stan. The Name Return Value Optimization [online]. Microsoft, 2004-02-03 [cit. 2009-03-23]. Dostupné online. 
  13. a b Glossary D Programming Language 2.0 [online]. Digital Mars [cit. 2009-03-23]. Dostupné online. 
  14. a b SHOUKRY, Ayman B. Named Return Value Optimization in Visual C++ 2005 [online]. Microsoft, October 2005 [cit. 2009-03-20]. Dostupné online. 
  15. Options Controlling C++ Dialect [online]. GCC, 2001-03-17 [cit. 2018-01-20]. Dostupné online. 
  16. HINNANT, Howard. N1377: A Proposal to Add Move Semantics Support to the C++ Language [online]. WG21, 2002-09-10 [cit. 2009-03-25]. Dostupné online. 

Externí odkazy

editovat