Najważniejszy const
W otchłaniach internetu natknąłem się na ciekawy post Herba Suttera z serii GotW: #88. W tym krótkim odcinku Herb opisuje mało znany sposób przedłużenia życia zmiennej tymczasowej jaką jest na przykład wartość zwracana z funkcji. Odsyłam oczywiście do oryginału, ja zaś i tak już krótki artykuł postaram się jeszcze bardziej streścić.
Generalnie wartość zwracaną z funkcji możemy przypisać do const referencji utworzonej na stosie w zakresie nadrzędnym, w ten sposób przedłużając jej czas życia:
struct Foo {};
Foo createFoo() {
Foo f;
return f;
} // przy wywołaniu jak poniżej obiekt f
// nie zostanie usunięty wraz z końcem funkcji
{
const Foo& extendedFoo = createFoo();
} // usunięcie obiektu nastąpi dopiero w tym miejscu
// (proszę zwrócić uwagę na sztucznie utworzony klamrami zakres)
Użycie const referencji jest tu kluczowe - przy nieconstowej referencji kod nie powinien się w ogóle skompilować. Tak samo kluczowe jest to, że ten trik zadziała tylko ze zmiennymi utworzonymi na stosie, a nie np. z atrybutami klasy (choćby były one const referencjami).
Jednak naprawdę interesująca rzecz się dzieje dopiero gdy w grę wchodzi dziedziczenie:
struct Derived : public Base {
~Derived() {}
};
Derived createDerived() {
return Derived();
}
const Base& obj = createDerived();
W takim przypadku gdy czas życia obiektu obj
dobiegnie końca, odpowiednie
destruktory (a więc najpierw ~Derived
, a po nim ~Base
) zostaną zawołane bez
konieczności wywołań wirtualnych (destruktor klasy bazowej nie musi być
wirtualnym). Herb w swoim artykule zwrócił uwagę, że powyższy fakt wykorzystał
Andrei Alexandrescu w swojej implementacji
ScopeGuarda,
nazywając ten mały niepozorny const
“najważniejszym constem, który napisał”:
typedef const ScopeGuardImplBase& ScopeGuard;
ScopeGuard guard = MakeGuard(/*...*/);