2013-08-21 17 views
16

I niedawno natknąłem się na to w jakiś dziwny funkcji klasy:Czy ten "wskaźnik" może być inny niż wskaźnik obiektu?

void* getThis() {return this;} 

A później w kodzie jest czasami używane tak: bla->getThis() (Gdzie bla jest wskaźnik do obiektu klasy gdzie ta funkcja jest zdefiniowana.) I nie mogę zdawać sobie sprawy z tego, co może być dobre. Czy istnieje jakakolwiek sytuacja, w której wskaźnik do obiektu byłby inny niż obiekt obiektu this (gdzie)?

Wygląda na to głupie pytanie, ale zastanawiam się, czy ja czegoś brakuje tutaj ..

+0

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

+0

W jakim kontekście nazywa się 'getThis'? – Joni

+6

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

Odpowiedz

17

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'; 
} 
+1

ooh. To może być wpis do konkursu na obfuskację kodu! – Dave

+2

@Dave: Jak słusznie zauważył Steve Clamage dawno temu, "obfuskany konkurs w C++ byłby jak strzelanie do ryby w beczce"! Istnieje jednak prawdziwe zastosowanie tej jawnej konwersji: możesz rzucić wskaźnik uzyskany z 'getThis()' na 'base const *'. Nie możesz zrobić tego samego ze wskaźnikiem uzyskanym z 'd2', konwertując go do' void const * '(możesz rzucić tak uzyskany wskaźnik na 'wyprowadzony2 const *', tzn. Cofnąć niejawną konwersję). –

+0

+1, ponieważ dziedziczenie wielokrotne jest jedynym scenariuszem, jaki mogę sobie wyobrazić, jeśli może to mieć sens. Wciąż nie mogę wymyślić praktycznego zastosowania, ale jeśli istnieje * jedna * rzecz, naprawdę wiem, że nic nie wiem. :) – syam

2

nr Tak, w określonych okolicznościach.

Wygląda na to, że jest to coś zainspirowane Smalltalk, w którym wszystkie obiekty mają metodę yourself. Są prawdopodobnie pewne sytuacje, w których powoduje to czyszczenie kodu. W komentarzu wygląda to na dziwny sposób na wdrożenie tego idiomu w C++.

W twoim konkretnym przypadku sprawdziłbym rzeczywiste użycie metody, aby zobaczyć, jak jest używana.

+0

+1 tylko dla części "grep". Szczerze mówiąc, w ciągu 20 + lat C++ nigdy nie widziałem sytuacji, w której ten konstrukt byłby potrzebny, ale może to tylko ja ... – syam

+0

@syam Nie mogę wymyślić przykładu, ale moje doświadczenie z smalltalk jest ograniczone. Zakładam, że stała się rzeczą w tym języku z jakiegoś powodu, który może lub nie może mieć zastosowania w C++. – Marcin

+0

Z 'void * p0 = bla; void * p1 = bla-> getThis(); 'możliwe jest, że' p0! = p1'! Wystarczy, że 'bla' jest wskaźnikiem do odpowiednio skonstruowanego typu. –

0

Napotkałem coś takiego wiele (wiele wiele) lat temu. Jeśli dobrze pamiętam, był potrzebny, gdy klasa manipuluje innymi instancjami tej samej klasy. Jednym z przykładów może być klasa kontenera, która może zawierać własny typ/(klasa?).

0

To może być sposób na zastąpienie tego słowa kluczowego. Powiedzmy, że masz pulę pamięci, pełną zainicjowaną na początku programu, na przykład wiesz, że w każdej chwili możesz obsłużyć maksymalnie 50 wiadomości, CMessage. Tworzysz pulę o rozmiarze 50 * sizeof (CMessage) (czymkolwiek ta klasa może być), a CMessage implementuje funkcję getThis.

W ten sposób zamiast przesłonić nowe słowo kluczowe, wystarczy nadpisać "to", uzyskując dostęp do puli. Może to również oznaczać, że obiekt może być zdefiniowany na różnych obszarach pamięci, powiedzmy na SRAM, w trybie rozruchu, a następnie na SDRAM.

Możliwe, że ta sama instancja zwróci różne wartości dla getThis przez program w takiej sytuacji, oczywiście w przypadku nadpisania.

1

Twoja klasa może mieć niestandardową wartość operator& (więc może nie zwrócić this z a). Dlatego istnieje std::addressof.

Powiązane problemy