Oczywiście wartości wskaźników mogą być różne! Poniżej przykład demonstrujący problem (może być konieczne użycie derived1
w systemie zamiast derived2
, aby uzyskać różnicę). Chodzi o to, że wskaźnik this
jest zwykle dostosowywany, gdy chodzi o wirtualne dziedziczenie z wieloma dziedziczeniami. Może to być rzadki przypadek, ale się zdarza.
Jeden potencjalny przypadek użycia tego idiomu jest w stanie przywrócić przedmiotów znanego typu po zapisaniu ich jako void const*
(lub void*
, a const
poprawności nie ma znaczenia tutaj): jeśli masz skomplikowaną hierarchię dziedziczenia nie można po prostu rzucić żadnego dziwnego wskaźnika na void*
i mam nadzieję, że uda się przywrócić oryginalny typ! To znaczy, aby łatwo uzyskać np. Wskaźnik do base
(z poniższego przykładu) i przekonwertować go na void*
, można nazwać p->getThis()
, który jest o wiele łatwiejszy do static_cast<base*>(p)
i uzyskać void*
, który można bezpiecznie odlać do base*
za pomocą a static_cast<base*>(v)
: możesz odwrócić niejawną konwersję, ale tylko wtedy, gdy powrócisz do tego samego typu, z którego pochodził oryginalny wskaźnik. Oznacza to, że static_cast<base*>(static_cast<void*>(d))
gdzie d
jest wskaźnikiem do obiektu typu pochodzącego od base
jest nielegalne, ale static_cast<base*>(d->getThis())
jest legalne.
Dlaczego adres zmienia się w pierwszej kolejności? W przykładzie base
jest wirtualną klasą bazową dwóch klas pochodnych, ale może być ich więcej. Wszystkie podobiekty, których klasa wirtualnie dziedziczy po base
, będą miały jeden wspólny obiekt base
w obiekcie innej pochodnej klasy (concrete
w poniższym przykładzie). Lokalizacja tego obiektu podrzędnego base
może być różna w stosunku do odpowiedniej pochodnej obiektu podrzędnego w zależności od sposobu sortowania różnych klas. W rezultacie wskaźnik do obiektu base
zasadniczo różni się od wskaźników do podobiektów klas wirtualnie dziedziczących po base
. Odpowiednie przesunięcie zostanie obliczone w czasie kompilacji, jeśli to możliwe, lub będzie pochodziło z czegoś podobnego do vtable w czasie wykonywania. Przesunięcia są korygowane podczas przekształcania wskaźników wzdłuż hierarchii dziedziczenia.
#include <iostream>
struct base
{
void const* getThis() const { return this; }
};
struct derived1
: virtual base
{
int a;
};
struct derived2
: virtual base
{
int b;
};
struct concrete
: derived1
, derived2
{
};
int main()
{
concrete c;
derived2* d2 = &c;
void const* dptr = d2;
void const* gptr = d2->getThis();
std::cout << "dptr=" << dptr << " gptr=" << gptr << '\n';
}
Czy są włączone szablony? Czy dziedziczenie? Czy typ zwrotu jest taki sam, jak klasa zawierająca funkcję? Ponieważ w przeciwnym razie nie ma sensu. –
W jakim kontekście nazywa się 'getThis'? – Joni
wygląda tak, jak w przypadku rzutowania na 'void *'. Dlaczego nie rzucili się do "void"? Nie mam pojęcia. Widzisz zabawne rzeczy w kodzie innych ludzi. – Dave