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(/*...*/);