6 Výkon, copy elision, SSO a vocabulary typy
- vysvětlit, proč je dynamická alokace drahá a větvení závisí na predikci
- rozlišit povinné a nepovinné vynechání kopie
- popsat SSO
- vybrat mezi
optional,expectedavariant
6.1 Ukázka: krátký řetězec nealokuje
#include <cassert>
#include <cstdint>
#include <string>
int main() {
std::string short_text = "ahoj";
std::string long_text(100, 'x');
auto inside = [](const std::string& s) {
auto object = reinterpret_cast<std::uintptr_t>(&s);
auto data = reinterpret_cast<std::uintptr_t>(s.data());
return data >= object && data < object + sizeof(s);
};
assert(inside(short_text));
assert(!inside(long_text));
}
Text je přímo v objektu, není alokace ani pomalý nepřímý přístup. To je Short String Optimization. V libstdc++ má std::string 32 bajtů a pojme 15 znaků.
6.2 Kde se ztrácí čas
- Dynamická alokace: volání alokátoru, režie a hlavně rozptýlená data, která nevedou k cache.
- Větvení: procesor odhaduje skok. Předvídatelná data jsou rychlá, náhodná stojí opakování pipeline. Proto bývá smyčka nad seřazeným polem rychlejší.
- Kontejnery:
std::vectormá data za sebou, a tak často porazístd::listi na vkládání.
6.3 Copy elision
Od C++17 prvalue nevytváří dočasný objekt, dokud to není nutné. T a = make(); s prvalue tedy nekopíruje nikdy a stačí, aby funkce vracela T. NRVO u pojmenovaného lokálního objektu je jen povolená optimalizace.
#include <cassert>
struct Counter {
static inline int copies = 0;
Counter() = default;
Counter(const Counter&) { ++copies; }
Counter(Counter&&) noexcept { ++copies; }
};
Counter prvalue() { return Counter{}; }
int main() {
Counter a = prvalue();
(void)a;
assert(Counter::copies == 0); // zaručeno od C++17
}
Proto nepiš return std::move(local);: změníš prvalue na xvalue a zakážeš NRVO.
6.4 Empty Base Optimization
Prázdná třída má jako samostatný objekt velikost 1. Jako předek ani s [[no_unique_address]] místo nezabere. Proto má std::unique_ptr s bezstavovým deleterem stejnou velikost jako ukazatel.
#include <memory>
static_assert(sizeof(std::unique_ptr<int>) == sizeof(int*));
6.5 Výběr vocabulary typu
| Typ | Kdy | Příklad |
|---|---|---|
std::optional<T> |
hodnota může chybět | hledání v seznamu |
std::expected<T, E> |
chyba s důvodem | parser |
std::variant<A, B> |
jedna z několika možností | stavy |
| výjimka | výjimečná situace | nedostatek paměti |
#include <cassert>
#include <expected>
#include <string>
std::expected<int, std::string> parse_digit(char c) {
if (c < '0' || c > '9') return std::unexpected("není číslice");
return c - '0';
}
int main() {
assert(parse_digit('7').value() == 7);
assert(parse_digit('x').error() == "není číslice");
}
Všechny tyto typy mají úložiště přímo v objektu, takže nealokují. Stojí na stejném principu jako kapitola 1.
6.6 Rozhraní knihovny
Soukromé členy jsou v hlavičce, takže změna jejich typu mění velikost třídy a rozbije binární kompatibilitu. PIMPL přesune detaily do .cpp za ukazatel. Cena je alokace a nepřímý přístup navíc.
6.7 Nedefinované chování
Překladač smí předpokládat, že k němu nedojde. Podle as-if pravidla smí udělat cokoli, co nezmění pozorovatelné chování platného programu. Přetečení int proto může smazat kontrolu v kódu.
6.8 Kvíz
6.9 Příklady na procvičení
Má T f() { T t; return t; } zaručené vynechání kopie? A T f() { return T{}; }?
Řešení
První je NRVO, jen povolené. Když se neuplatní, vezme se přesun. Druhé je prvalue, tam je to od C++17 zaručené.
Jak velký je std::unique_ptr s bezstavovým lambda deleterem?
Řešení
Stejně jako ukazatel, protože prázdný deleter nezabírá místo.
#include <memory>
auto deleter = [](int* p) { delete p; };
static_assert(sizeof(std::unique_ptr<int, decltype(deleter)>) == sizeof(int*));
6.10 Tahák
- Alokace a nepředvídatelné větvení jsou nejdražší.
- Prvalue: bez kopie zaručeně. NRVO: nezaručené.
- Nikdy
return std::move(local). - EBO: prázdný předek nezabírá místo.
- SSO: krátké řetězce v objektu.
optionalchybí,expectedchyba,variantjedna z.- PIMPL: stabilní ABI za cenu nepřímosti.