19.5 -- Wskaźniki pustki

Klasa void pointer, znany również jako wskaźnik ogólny, to specjalny typ wskaźnika, który można wskazać na obiekty zawierające dowolne dane wpisz! Wskaźnik void deklaruje się jak zwykły wskaźnik, używając słowa kluczowego void jako typu wskaźnika:

void* ptr {}; // ptr jest wskaźnikiem pustki

Wskaźnik void może wskazywać na obiekty dowolnego typu danych:

int nValue {};
float fValue {};

struct Something
{
    int n;
    float f;
};

Something sValue {};

void* ptr {};
ptr = &nValue; // valid
ptr = &fValue; // valid
ptr = &sValue; // valid

Jednakże, ponieważ wskaźnik void nie wie, na jaki typ obiektu wskazuje, dereferencja wskaźnika void jest niedozwolona. Zamiast tego wskaźnik void musi najpierw zostać rzutowany na inny typ wskaźnika, zanim będzie można wykonać dereferencję.

int value{ 5 };
void* voidPtr{ &value };

// std::cout << *voidPtr << '\n'; // nielegalne: dereferencja wskaźnika pustki

int* intPtr{ static_cast<int*>(voidPtr) }; // jednak jeśli rzucimy nasz wskaźnik void na wskaźnik int...

std::cout << *intPtr << '\n'; // wtedy możemy wyrejestrować wynik

Wypisuje:

5

Następne oczywiste pytanie brzmi: Jeśli wskaźnik void nie wie, na co wskazuje, skąd mamy wiedzieć, na co go rzucić? Ostatecznie to zależy od Ciebie.

Oto przykład używanego wskaźnika pustki:

#include <cassert>
#include <iostream>

enum class Type
{
    tInt, // uwaga: nie możemy tutaj użyć „int”, ponieważ jest to słowo kluczowe, więc zamiast tego użyjemy „tInt”
    tFloat,
    tCString
};

void printValue(void* ptr, Type type)
{
    switch (type)
    {
    case Type::tInt:
        std::cout << *static_cast<int*>(ptr) << '\n'; // rzutuj na int wskaźnik i wykonaj pośrednictwo
        break;
    case Type::tFloat:
        std::cout << *static_cast<float*>(ptr) << '\n'; // rzut na float wskaźnik i wykonaj pośredniość
        break;
    case Type::tCString:
        std::cout << static_cast<char*>(ptr) << '\n'; // rzut na wskaźnik char (bez pośredniości)
        // std::cout będzie traktował znak* jako ciąg w stylu C
        // jeśli mielibyśmy wykonać pośrednie przez wyniku, to po prostu wypiszemy pojedynczy znak, na który wskazuje ptr
        break;
    default:
        std::cerr << "printValue(): invalid type provided\n"; 
        assert(false && "type not found");
        break;
    }
}

int main()
{
    int nValue{ 5 };
    float fValue{ 7.5f };
    char szValue[]{ "Mollie" };

    printValue(&nValue, Type::tInt);
    printValue(&fValue, Type::tFloat);
    printValue(szValue, Type::tCString);

    return 0;
}

Ten program wypisuje:

5
7.5
Mollie

Różne wskaźniki pustki

Wskaźniki pustki można ustawić na wartość null:

void* ptr{ nullptr }; // ptr jest wskaźnikiem pustki, który obecnie jest wskaźnikiem zerowym

Ponieważ wskaźnik pustki nie wie, jaki typ obiektu wskazuje, usunięcie wskaźnika pustki spowoduje niezdefiniowane zachowanie. Jeśli chcesz usunąć wskaźnik pustki, static_cast przywróć najpierw odpowiedni typ.

Nie jest możliwe wykonanie arytmetyki wskaźnika na wskaźniku pustki. Dzieje się tak, ponieważ arytmetyka wskaźników wymaga, aby wskaźnik wiedział, jaki rozmiar obiektu wskazuje, dzięki czemu może odpowiednio zwiększać lub zmniejszać wskaźnik.

Zauważ, że nie ma czegoś takiego jak odwołanie do pustki. Dzieje się tak dlatego, że odwołanie do void byłoby typu void & i nie wiedziałoby, do jakiego typu wartości się odwołuje.

Wnioski

Ogólnie rzecz biorąc, dobrym pomysłem jest unikanie używania wskaźników void, chyba że jest to absolutnie konieczne, ponieważ skutecznie pozwalają one uniknąć sprawdzania typu. Dzięki temu możesz niechcący zrobić rzeczy, które nie mają sensu, a kompilator nie będzie na to narzekał. Na przykład poprawne byłoby następujące działanie:

    int nValue{ 5 };
    printValue(&nValue, Type::tCString);

Ale kto wie, jaki byłby faktycznie wynik!

Chociaż powyższa funkcja wydaje się dobrym sposobem na sprawienie, aby pojedyncza funkcja obsługiwała wiele typów danych, w rzeczywistości C++ oferuje znacznie lepszy sposób na zrobienie tego samego (poprzez przeciążenie funkcji), który zachowuje sprawdzanie typu, aby zapobiec nadużyciom. W wielu innych miejscach, w których kiedyś używano wskaźników void do obsługi wielu typów danych, obecnie lepiej jest to zrobić przy użyciu szablonów, które również oferują silne sprawdzanie typu.

Jednak bardzo rzadko wskaźnik void może nadal znaleźć rozsądne zastosowanie. Tylko upewnij się, że nie ma lepszego (bezpieczniejszego) sposobu na wykonanie tego samego, używając najpierw innych mechanizmów językowych!

Czas quizu

Pytanie nr 1

Jaka jest różnica między wskaźnikiem void a wskaźnikiem zerowym?

Pokaż rozwiązanie

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
183 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze