Для чего нужна множественная косвенность в C++?
Вопрос
При каких обстоятельствах вам может потребоваться использовать множественную косвенность (то есть цепочку указателей, как в Foo **
) на С++?
Решение
Наиболее распространенное использование, как указал @aku, - это разрешить изменение параметра указателя, которое будет видимым после возврата функции.
#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;
}
Это будет напечатано
12
Но есть и несколько других полезных применений, как в следующем примере: перебор массива строк и вывод их на стандартный вывод.
#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;
}
Другие советы
Наиболее распространенное использование IMO - передача ссылки на переменную указателя.
void test(int ** var)
{
...
}
int *foo = ...
test(&foo);
Вы можете создать многомерный неровный массив, используя двойные указатели:
int ** array = new *int[2];
array[0] = new int[2];
array[1] = new int[3];
Одним из распространенных сценариев является ситуация, когда вам необходимо передать нулевой указатель на функцию, инициализировать его внутри этой функции и использовать вне функции.Без косвенности multiplie вызывающая функция никогда не имела бы доступа к инициализированному объекту.
Рассмотрим следующую функцию:
initialize(foo* my_foo)
{
my_foo = new Foo();
}
Любая функция, вызывающая «initialize(foo*)», не будет иметь доступа к инициализированному экземпляру Фу, поскольку указатель, передаваемый этой функции, является копией.(В конце концов, указатель — это просто целое число, а целые числа передаются по значению.)
Однако, если функция была определена следующим образом:
initialize(foo** my_foo)
{
*my_foo = new Foo();
}
...и это называлось так...
Foo* my_foo;
initialize(&my_foo);
... тогда вызывающий абонент будет иметь доступ к инициализированному экземпляру через 'my_foo' - потому что это адрес указателя, который был передан для «инициализации».
Конечно, в моем упрощенном примере функция инициализации могла бы просто вернуть вновь созданный экземпляр через ключевое слово return, но это не всегда подходит — возможно, функции нужно вернуть что-то еще.
Если вы передаете указатель в качестве выходного параметра, вы можете передать его как Foo**
и установите его значение как *ppFoo = pSomeOtherFoo
.
А из отдела алгоритмов и структур данных вы можете использовать эту двойную косвенность для обновления указателей, что может быть быстрее, чем, например, замена реальных объектов.
Простым примером может быть использование int** foo_mat
как двумерный массив целых чисел.Или вы также можете использовать указатели на указатели - допустим, у вас есть указатель. void* foo
и у вас есть два разных объекта, которые имеют ссылку на него со следующими членами: void** foo_pointer1
и void** foo_pointer2
, имея указатель на указатель, вы действительно можете проверить, является ли *foo_pointer1 == NULL
что указывает на то, что foo имеет значение NULL.Вы не сможете проверить, имеет ли foo значение NULL, если foo_pointer1 был обычным указателем.Надеюсь, мое объяснение не было слишком сумбурным :)
Карл:Ваш пример должен быть:
*p = x;
(У вас две звезды.) :-)
В C эта идиома абсолютно необходима.Рассмотрим проблему, в которой вы хотите, чтобы функция добавляла строку (чистый C, то есть char *) в массив указателей на char *.Прототип функции требует трех уровней косвенности:
int AddStringToList(unsigned int *count_ptr, char ***list_ptr, const char *string_to_add);
Мы называем это следующим образом:
unsigned int the_count = 0;
char **the_list = NULL;
AddStringToList(&the_count, &the_list, "The string I'm adding");
В C++ у нас есть возможность использовать вместо этого ссылки, что приведет к другой сигнатуре.Но нам все еще нужны два уровня косвенности, о которых вы спрашивали в своем исходном вопросе:
int AddStringToList(unsigned int &count_ptr, char **&list_ptr, const char *string_to_add);
Обычно, когда вы передаете указатель на функцию в качестве возвращаемого значения:
ErrorCode AllocateObject (void **object);
где функция возвращает код ошибки успеха/неудачи и заполняет параметр объекта указателем на новый объект:
*object = new Object;
Это часто используется в COM-программировании в Win32.
Это больше похоже на C: в C++ вы часто можете обернуть этот тип системы в класс, чтобы сделать код более читабельным.