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 is a void pointer

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'; // illegal: dereference of void pointer

int* intPtr{ static_cast<int*>(voidPtr) }; // however, if we cast our void pointer to an int pointer...

std::cout << *intPtr << '\n'; // then we can dereference the result

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, // note: we can't use "int" here because it's a keyword, so we'll use "tInt" instead
    tFloat,
    tCString
};

void printValue(void* ptr, Type type)
{
    switch (type)
    {
    case Type::tInt:
        std::cout << *static_cast<int*>(ptr) << '\n'; // cast to int pointer and perform indirection
        break;
    case Type::tFloat:
        std::cout << *static_cast<float*>(ptr) << '\n'; // cast to float pointer and perform indirection
        break;
    case Type::tCString:
        std::cout << static_cast<char*>(ptr) << '\n'; // cast to char pointer (no indirection)
        // std::cout will treat char* as a C-style string
        // if we were to perform indirection through the result, then we'd just print the single char that ptr is pointing to
        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 is a void pointer that is currently a null pointer

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