Vypuštění kopírování () 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.ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §12.8 Copying class objects [class.copy] para. 15 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. Optimalizaci nelze uplatnit na dočasný objekt, které byl svázán s referencí.

Příklad #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,ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §15.1 Throwing an exception [except.throw] para. 5ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §15.3 Handling an exception [except.handle] para. 17 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. 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.

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í.

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

Shrnutí 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á .ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §1.9 Program execution [intro.execution] para. 1 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í konstruktor má postranní efekty.ISO/IEC (2003). ISO/IEC 14882:2003(E): Programming Languages - C++ §12.8 Copying class objects [class.copy] para. 15

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). 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í 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. Tedy následující kód:

struct Data { char bytes;

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;

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. 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:

struct Data { char bytes;

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++. Později byla tato technika pojmenována „optimalizace návratové hodnoty“ (NRVO).

Podpora v překladačích Optimalizaci návratové hodnoty podporuje většina překladačů. 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í:

#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(); }

Odkazy

Reference

Externí odkazy Copy elision on cppreference.com

Kategorie:Články s ukázkami kódu v C++ Kategorie:C++