Czy istnieje uzasadnione użycie void*
w C ++? A może zostało to wprowadzone, ponieważ C to miał?
Podsumowując moje przemyślenia:
Dane wejściowe : jeśli chcemy zezwolić na wiele typów danych wejściowych, możemy przeciążać funkcje i metody, alternatywnie możemy zdefiniować wspólną klasę bazową lub szablon (dziękujemy za wspomnienie o tym w odpowiedziach). W obu przypadkach kod jest bardziej opisowy i mniej podatny na błędy (pod warunkiem, że klasa bazowa jest zaimplementowana w rozsądny sposób).
Wynik : nie przychodzi mi do głowy żadna sytuacja, w której wolałbym otrzymywać, void*
w przeciwieństwie do czegoś pochodzącego ze znanej klasy bazowej.
Żeby wyjaśnić, co mam na myśli: nie pytam konkretnie, czy istnieje przypadek użycia void*
, ale czy istnieje przypadek, w którym void*
jest najlepszy lub jedyny dostępny wybór. Na co kilka osób poniżej doskonale odpowiedziało.
variant
,any
, rekord z wariantami. Wszystko, co wskazuje rzeczywisty typ treści i jest bezpieczniejsze w użyciu.Odpowiedzi:
void*
jest co najmniej konieczny jako wynik::operator new
(również everyoperator new
...)malloc
i jako argumentnew
operatora umieszczania .void*
można uważać za wspólny nadtyp każdego typu wskaźnika. Więc nie jest to dokładnie wskaźnik do celuvoid
, ale wskaźnik do czegokolwiek.BTW, jeśli chcesz zachować jakieś dane dla kilku niepowiązanych zmiennych globalnych, możesz użyć ich
std::map<void*,int> score;
wtedy, po zadeklarowaniu globalnychint x;
idouble y;
istd::string s;
zróbscore[&x]=1;
iscore[&y]=2;
iscore[&z]=3;
memset
chcevoid*
adresu (najbardziej ogólnego)Ponadto systemy POSIX mają dlsym i jego typem zwracanym powinien być
void*
źródło
char *
. Są bardzo blisko w tym aspekcie, chociaż znaczenie jest nieco inne.C++
jest wC++
tym napisane, to wystarczyvoid*
. Uwielbiam tę stronę. Zadaj jedno pytanie, wiele się naucz.void*
typ. Wszystkie używane standardowe funkcje bibliotekichar*
Istnieje wiele powodów, dla których warto używać
void*
, z których 3 najczęściej to:void*
jej interfejsuW odwrotnej kolejności oznaczanie niepisanej pamięci za pomocą
void*
(3) zamiastchar*
(lub wariantów) pomaga zapobiegać przypadkowej arytmetyce wskaźnika; jest bardzo mało dostępnych operacji,void*
więc zwykle wymaga odlewania, zanim będzie użyteczny. I oczywiście podobnie jak wchar*
przypadku aliasingu nie ma problemu.Type-erasure (2) jest nadal używany w C ++, w połączeniu z szablonami lub nie:
std::function
I oczywiście, gdy interfejs, z którym masz do czynienia, używa
void*
(1), masz niewielki wybór.źródło
O tak. Nawet w C ++ czasami idziemy
void *
raczej z niżtemplate<class T*>
dlatego, że czasami dodatkowy kod z rozszerzenia szablonu waży zbyt dużo.Zwykle używałbym go jako rzeczywistej implementacji typu, a typ szablonu odziedziczyłby po nim i zawinąłby rzutowania.
Ponadto muszą być używane niestandardowe podzielniki płyt (nowe implementacje operatorów)
void *
. Jest to jeden z powodów, dla których g ++ dodał rozszerzenie pozwalające na arytmatyzację wskaźnikavoid *
tak, jakby miał rozmiar 1.źródło
struct wrapper_base {}; template<class T> struct wrapper : public wrapper_base {T val;} typedef wrapper* like_void_ptr;
to minimalny symulator void - * - wykorzystujący szablony.Prawdziwe.
Jest to częściowo prawda: a co, jeśli nie możesz zdefiniować wspólnej klasy bazowej, interfejsu lub podobnego? Aby je zdefiniować, musisz mieć dostęp do kodu źródłowego, co często nie jest możliwe.
Nie wspomniałeś o szablonach. Jednak szablony nie mogą pomóc w polimorfizmie: działają z typami statycznymi, tj. Znanymi w czasie kompilacji.
void*
można uznać za najniższy wspólny mianownik. W C ++ zazwyczaj nie jest to potrzebne, ponieważ (i) z natury nie można z nim zrobić zbyt wiele i (ii) prawie zawsze są lepsze rozwiązania.Co więcej, zazwyczaj kończy się konwersją na inne typy betonu. Dlatego
char *
jest zwykle lepszy, chociaż może to oznaczać, że spodziewasz się łańcucha w stylu C, a nie czystego bloku danych. Dlategovoid*
jest lepsze niżchar*
do tego, ponieważ umożliwia niejawne rzutowanie z innych typów wskaźników.Powinieneś otrzymać jakieś dane, popracować z nimi i wygenerować wynik; Aby to osiągnąć, musisz znać dane, z którymi pracujesz, w przeciwnym razie masz inny problem, który nie jest tym, który pierwotnie rozwiązałeś. Na przykład wiele języków nie ma
void*
i nie ma z tym problemu.Kolejne legalne użycie
Podczas drukowania adresów wskaźników z funkcjami takimi jak
printf
wskaźnik powinny miećvoid*
typ i dlatego może być konieczne rzutowanie navoid
*źródło
Tak, jest tak samo przydatna jak każda inna rzecz w języku.
Na przykład możesz go użyć do usunięcia typu klasy, którą możesz statycznie rzutować na odpowiedni typ w razie potrzeby, aby uzyskać minimalny i elastyczny interfejs.
W tej odpowiedzi jest przykład użycia, który powinien dać ci pomysł.
Kopiuję i wklejam poniżej dla zachowania przejrzystości:
class Dispatcher { Dispatcher() { } template<class C, void(C::*M)() = C::receive> static void invoke(void *instance) { (static_cast<C*>(instance)->*M)(); } public: template<class C, void(C::*M)() = &C::receive> static Dispatcher create(C *instance) { Dispatcher d; d.fn = &invoke<C, M>; d.instance = instance; return d; } void operator()() { (fn)(instance); } private: using Fn = void(*)(void *); Fn fn; void *instance; };
Oczywiście jest to tylko jedno z wielu zastosowań
void*
.źródło
Współpraca z zewnętrzną funkcją biblioteki, która zwraca wskaźnik. Oto jeden dla aplikacji Ada.
extern "C" { void* ada_function();} void* m_status_ptr = ada_function();
To zwraca wskaźnik do tego, o czym Ada chciała ci powiedzieć. Nie musisz z tym robić nic wymyślnego, możesz oddać go Adzie, aby zrobiła następną rzecz. W rzeczywistości rozplątanie wskaźnika Ada w C ++ jest nietrywialne.
źródło
auto
zamiast tego w tym przypadku? Zakładając, że typ jest znany w czasie kompilacji.Krótko mówiąc, C ++ jako język ścisły (nie biorąc pod uwagę reliktów C, takich jak malloc () ) wymaga void *, ponieważ nie ma wspólnego rodzica wszystkich możliwych typów. W przeciwieństwie na przykład do ObjC, który ma obiekt .
źródło
malloc
inew
oba wracająvoid *
, więc potrzebowałbyś, gdyby nawet istniała klasa obiektów w C ++operator new()
zwracavoid *
, alenew
wyrażenie nieint
2. Jeśli jest to nie-wirtualny EBC, to czym się różni odvoid*
?Pierwszą rzeczą, która przychodzi mi do głowy (podejrzewam, że jest to konkretny przypadek kilku powyższych odpowiedzi), jest możliwość przekazania instancji obiektu do procedury wątku w systemie Windows.
Mam kilka klas C ++, które muszą to zrobić, mają implementacje wątku roboczego, a parametr LPVOID w API CreateThread () otrzymuje adres implementacji metody statycznej w klasie, dzięki czemu wątek roboczy może wykonać pracę z konkretna instancja klasy. Proste rzutowanie statyczne z powrotem w Threadproc daje wystąpienie do pracy, umożliwiając każdemu obiektowi, którego wystąpienie, wątek roboczy z pojedynczej implementacji metody statycznej.
źródło
W przypadku dziedziczenia wielokrotnego, jeśli chcesz uzyskać wskaźnik do pierwszego bajtu fragmentu pamięci zajmowanego przez obiekt, możesz to
dynamic_cast
zrobićvoid*
.źródło