A che serve l'indirizzamento multiplo in C++?
Domanda
In quali circostanze potresti voler utilizzare più riferimenti indiretti (ovvero una catena di puntatori come in Foo **
) in C++?
Soluzione
L'utilizzo più comune, come sottolineato da @aku, è consentire che una modifica a un parametro del puntatore sia visibile dopo il ritorno della funzione.
#include <iostream>
using namespace std;
struct Foo {
int a;
};
void CreateFoo(Foo** p) {
*p = new Foo();
(*p)->a = 12;
}
int main(int argc, char* argv[])
{
Foo* p = NULL;
CreateFoo(&p);
cout << p->a << endl;
delete p;
return 0;
}
Questo verrà stampato
12
Ma ci sono molti altri usi utili come nell'esempio seguente per ripetere un array di stringhe e stamparle sullo standard output.
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
const char* words[] = { "first", "second", NULL };
for (const char** p = words; *p != NULL; ++p) {
cout << *p << endl;
}
return 0;
}
Altri suggerimenti
L'utilizzo più comune dell'IMO è passare il riferimento alla variabile puntatore
void test(int ** var)
{
...
}
int *foo = ...
test(&foo);
Puoi creare un array frastagliato multidimensionale utilizzando doppi puntatori:
int ** array = new *int[2];
array[0] = new int[2];
array[1] = new int[3];
Uno scenario comune è quello in cui è necessario superare un file nullo puntatore a una funzione e averlo inizializzato all'interno di quella funzione e utilizzato all'esterno della funzione.Senza l'indirizzamento multiplo, la funzione chiamante non avrebbe mai accesso all'oggetto inizializzato.
Consideriamo la seguente funzione:
initialize(foo* my_foo)
{
my_foo = new Foo();
}
Qualsiasi funzione che chiama 'initialize(foo*)' non avrà accesso all'istanza inizializzata di Pippo, perché il puntatore passato a questa funzione è una copia.(Dopo tutto il puntatore è solo un numero intero e gli interi vengono passati per valore.)
Tuttavia, se la funzione fosse definita in questo modo:
initialize(foo** my_foo)
{
*my_foo = new Foo();
}
...e si chiamava così...
Foo* my_foo;
initialize(&my_foo);
...allora il chiamante avrebbe accesso all'istanza inizializzata, tramite 'my_foo' - perché è il indirizzo del puntatore passato per "inizializzare".
Naturalmente, nel mio esempio semplificato, la funzione 'initialize' potrebbe semplicemente restituire l'istanza appena creata tramite la parola chiave return, ma ciò non sempre è adatto: forse la funzione deve restituire qualcos'altro.
Se passi un puntatore come parametro di output, potresti volerlo passare come Foo**
e impostarne il valore come *ppFoo = pSomeOtherFoo
.
E dal reparto algoritmi e strutture dati, puoi utilizzare questo doppio riferimento indiretto per aggiornare i puntatori, il che può essere più veloce rispetto, ad esempio, allo scambio di oggetti reali.
Un semplice esempio potrebbe essere l'utilizzo int** foo_mat
come array 2d di numeri interi.Oppure puoi anche usare puntatori a puntatori: diciamo che hai un puntatore void* foo
e hai 2 oggetti diversi che hanno un riferimento ad esso con i seguenti membri: void** foo_pointer1
E void** foo_pointer2
, avendo un puntatore a un puntatore puoi effettivamente verificare se *foo_pointer1 == NULL
che indica che foo è NULL.Non saresti in grado di verificare se foo è NULL se foo_pointer1 fosse un puntatore normale.Spero che la mia spiegazione non sia stata troppo confusa :)
Carlo:Il tuo esempio dovrebbe essere:
*p = x;
(Hai due stelle.) :-)
In C l'idioma è assolutamente obbligatorio.Considera il problema in cui vuoi che una funzione aggiunga una stringa (C puro, quindi un char *) a un array di puntatori a char *.Il prototipo della funzione richiede tre livelli di indirezione:
int AddStringToList(unsigned int *count_ptr, char ***list_ptr, const char *string_to_add);
Lo chiamiamo così:
unsigned int the_count = 0;
char **the_list = NULL;
AddStringToList(&the_count, &the_list, "The string I'm adding");
In C++ abbiamo invece la possibilità di utilizzare i riferimenti, che produrrebbero una firma diversa.Ma abbiamo ancora bisogno dei due livelli di riferimento indiretto che hai chiesto nella tua domanda originale:
int AddStringToList(unsigned int &count_ptr, char **&list_ptr, const char *string_to_add);
Di solito quando si passa un puntatore a una funzione come valore restituito:
ErrorCode AllocateObject (void **object);
dove la funzione restituisce un codice di errore di successo/fallimento e compila il parametro dell'oggetto con un puntatore al nuovo oggetto:
*object = new Object;
Questo è molto utilizzato nella programmazione COM in Win32.
Questa è più una cosa da fare in C, in C++ puoi spesso racchiudere questo tipo di sistema in una classe per rendere il codice più leggibile.