Что подразумевается в void **?

Когда я развивается в COM, я всегда вижу преобразование типа (void **), как показано ниже.

QueryInterface(/* [in] */ REFIID riid,/* [out] */ void** ppInterface)

Какое точное значение?

IMHO, он сообщает компилятору не применять проверку типов, поскольку тип, который указывает ppInterface, не известен коду клиента во время компиляции.

Спасибо ~~~

Обновление 1

Я так понимаю:

void * p подразумевает AnyType * p

void ** pp подразумевает указатель на AnyType *

Обновление 2

Если void ** pp означает "указатель на void *", то какие проверки делает компилятор, когда он видит его?

Ответ 1

Причина, по которой COM использует void** с QueryInterface, несколько особенна. (См. Ниже.)

Как правило, void** просто означает указатель на void*, и его можно использовать для выходных параметров, т.е. параметры, указывающие место, где функция может вернуть значение. Ваш комментарий /* [out] */ указывает, что место, на которое указывает ppvInterface, будет записано.

"Почему параметры с типом указателя используются как параметры?", спросите вы? Помните, что вы можете изменить две вещи с помощью указательной переменной:

  • Вы можете изменить сам указатель, чтобы он указывал на другой объект. (ptr = ...)
  • Вы можете изменить заостренный объект. (*ptr = ...)

Указатели передаются функции по значению, т.е. функция получает свою собственную локальную копию исходного указателя, который был передан ей. Это означает, что вы можете изменить параметр указателя внутри функции (1), не влияя на исходный указатель, поскольку изменена только локальная копия. Тем не менее, вы можете изменить заостренный объект (2), и это будет видно за пределами функции, потому что копия имеет то же значение, что и исходный указатель и, таким образом, ссылается на один и тот же объект.

Теперь о COM специально:

  • Указатель на интерфейс (указанный riid) будет возвращен в переменной, на которую ссылается ppvInterface. QueryInterface достигает этого через механизм (2), упомянутый выше.

  • С void** требуется один *, чтобы разрешить механизм (2); другой * отражает тот факт, что QueryInterface не возвращает вновь созданный объект (IUnknown), а уже существующий: во избежание дублирования этого объекта указатель на этот объект (IUnknown*) возвращается.

  • Если вы спрашиваете, почему ppvInterface имеет тип void**, а не IUnknown**, что выглядит более разумным по типу безопасности (так как все интерфейсы должны выводиться из IUnknown), тогда прочитайте следующий аргумент, взятый из книги Essential COM by Don Box, p. 60 (глава Тип Принуждение и IUnknown):


Одна дополнительная тонкость, связанная с QueryInterface, относится к ее второму параметру, который имеет тип void **. Очень иронично, что QueryInterface, основа системы COM-типа, имеет довольно непрозрачный прототип типа в С++ [...]

 IPug *pPug = 0;
 hr = punk->QueryInterface(IID_IPug, (void**)&pPug);

К сожалению, следующее выглядит так же корректно для компилятора С++:

 IPug *pPug = 0;
 hr = punk->QueryInterface(IID_ICat, (void**)&pPug);

Эта более тонкая вариация также правильно компилируется:

 IPug *pPug = 0;
 hr = punk->QueryInterface(IID_ICat, (void**)pPug);

Учитывая, что правила наследования не применяются к указателям, это альтернативное определение QueryInterface не устраняет проблему:

 HRESULT QueryInterface(REFIID riid, IUnknown** ppv);

То же ограничение распространяется и на ссылки на указатели. Следующее альтернативное определение, возможно, более удобно для клиентов:

 HRESULT QueryInterface(const IID& riid, void* ppv);

[...] К сожалению, это решение не уменьшает количество ошибок [...] и , устраняя необходимость в литье, удаляет визуальный индикатор того, что безопасность типа С++ может оказаться под угрозой. Учитывая желаемую семантику QueryInterface, типы аргументов, выбранных Microsoft, являются разумными, если они не являются безопасными или элегантными. [...]

Ответ 2

A void ** является указателем на void *. Это можно использовать для передачи адреса переменной void *, которая будет использоваться в качестве выходного параметра - например:

void alloc_two(int n, void **a, void **b)
{
    *a = malloc(n * 100);
    *b = malloc(n * 200);
}

/* ... */

void *x;
void *y;

alloc_two(10, &x, &y);

Ответ 3

Это только указатель на void*.

Например:

Something* foo;
Bar((void**)&foo);

// now foo points to something meaningful

Изменить: Возможная реализация в С#.

  struct Foo { }

  static Foo foo = new Foo();

  unsafe static void Main(string[] args)
  {
    Foo* foo;

    Bar((void**)&foo);
  }

  static unsafe void Bar(void** v)
  {
    fixed (Foo* f = &foo)
    {
      *v = f;
    }
  }

Ответ 4

Передача с помощью void * также гарантирует, что указанный объект не может быть удален или изменен (случайно).

"Это означает, что объект не может быть удален с помощью указателя типа void *, потому что нет объектов типа void."

Ответ 5

Это указатель на указатель интерфейса, который вы запрашиваете с помощью этого вызова. Очевидно, вы можете запросить всевозможные интерфейсы, поэтому он должен быть указателем на пустоту. Если интерфейс не существует, указатель имеет значение NULL.

edit: Подробную информацию можно найти здесь: http://msdn.microsoft.com/en-us/library/ms682521(VS.85).aspx

Ответ 6

Он позволяет API указывать, что указатель может использоваться в качестве параметра [in-out] в будущем, но пока этот указатель не используется. (Обычно значение NULL является требуемым значением.)

При возврате одного из многих возможных типов, не имеющих общего супертипа (например, с QueryInterface), возврат void * на самом деле является единственным вариантом, и поскольку это необходимо передать как параметр [out], указатель на этот тип (void **).

Ответ 7

не применять проверку типов

В самом деле, void* или void** позволяют использовать разные типы указателей, которые могут быть отключены до void*, чтобы вписаться в тип параметров функции.

Ответ 8

Указатель на указатель неизвестного интерфейса, который может быть предоставлен.

Ответ 9

Вместо того, чтобы использовать указатели для указателей, попробуйте использовать ссылку на указатель. Это немного больше С++, чем использование **.

например.

void Initialise(MyType &*pType)
{
    pType = new MyType();
}