pointer passing pass and c++ reference void-pointers

c++ - passing by reference and pointer



¿Por qué es imposible tener una referencia de anulación? (10)

Aquí hay un resumen de las diferentes cosas que se han dicho y en las que he pensado.

Dos razones principales por las que se prohíbe la referencia al vacío

1 Habrían sido totalmente inútiles.

De hecho, si miramos hacia atrás en los tiempos de C, los punteros vacíos tenían dos propósitos:

  • Manejo de la memoria (por ejemplo, malloc)
  • Genericity (funciones de escritura que pueden aceptar cualquier tipo de argumentos)

Cuando salió C ++, las plantillas se convirtieron en la mejor solución para implementar la genérica. Sin embargo, la gestión de memoria personalizada aún tenía que ser posible, y la interoperabilidad entre C ++ y C era una preocupación importante, por lo que el vacío * se mantuvo. Una referencia hipotética de vacío no sería de ayuda con la gestión de memoria, y la genérico ya está cubierta, por lo que básicamente no tendría prácticamente ningún uso (excepto la garantía de no nulidad que se describe a continuación).

2 No podría hacer nada con eso

Cuando se utiliza un puntero de vacío, no se permite desreferenciarlo; transpuesta al caso de las referencias, eso significa que no puede usar la referencia vacía (siempre hipotética). Asi que

void *data = // something // using *data and data-> is forbidden void &data = // something // using data is forbidden

Sin embargo, podríamos pensar en un caso de uso en el que la referencia no tenga que ser "desreferenciada" (esta frase es terriblemente incorrecta, pero entiendes mi punto), pero donde solo tomaríamos su dirección. Supongamos que tengo la siguiente función:

void foo(void *dataptr) { assert(dataptr != NULL); // or != 0 // do something with dataptr }

Para evitar esta afirmación molesta, podría escribir la función de esta manera:

void foo(void &dataref) { void *data = &dataref; // do something with data }

Sin embargo, para que esto funcione, &dataref necesita ser equivalente a dataptr , que no es el caso : &dataref es equivalente a &*dataptr !

Por lo tanto, incluso tomar la dirección implica una desreferencia, al menos conceptualmente (detrás de escena, la primera equivalencia es probablemente verdadera, pero a nivel semántico no lo es). En consecuencia, no hay absolutamente ningún uso que podamos hacer de los datos, por lo que las referencias vacías son una aberración.

¿Por qué es imposible tener una referencia al vacío? Lo único que encontré en el Estándar C ++ es esta línea, en 8.3.2.1

Un declarante que especifica el tipo "referencia a cv void" está mal formado.

¿Por qué es así? ¿Por qué no puedo escribir una función "genérica" ​​que acepte un void& ?

Para que quede claro, no tengo una aplicación útil en mente en la que usar una referencia de vacío sea mejor que usar plantillas, pero tengo curiosidad acerca de los motivos para prohibir esta construcción.

Para aclarar un poco, entiendo que usar una referencia de anulación "tal como está" sería tan insignificante como desreferenciar un puntero-a-vacío. Sin embargo, podría convertirlo en una referencia a algún tipo para usarlo, ¿o no? De hecho, no veo por qué el siguiente fragmento puede funcionar ...

void foo(void *data) { int *i = reinterpret_cast<int*>(data); // do something with i }

... mientras que este no puede:

void foo(void &data) { int &i = reinterpret_cast<int&>(data); // do something with i }


Primero pregúntate a ti mismo, ¿cómo harías referencia a un puntero vacío?

void *p = /*something*/ ; cout << *p << endl;

El código anterior no tiene sentido, una de las razones por las que tenemos vacío es por lo que podemos decir "Necesito hacer aquí un trabajo de puntero genérico, y no sé ni me importa lo que estoy señalando". Por definición, el compilador no sabe a qué apunta un vacío *, por lo tanto no puede desreferenciarlo. Puedes, mediante casting, pero el compilador no puede.

Una referencia a un vacío sufre del mismo problema, por definición, los datos apuntados no tienen un tipo, por lo tanto, no pueden ser referenciados de ninguna manera significativa.

Para referenciarlo usted - el programador - necesita convertirlo a otro tipo, entonces puede tener una referencia tipeada a él.

No estoy seguro de haber explicado esto tan bien como quería.

Rubén, ¿algún pensamiento?

EDITAR: Para responder a su edición.

Tome la primera función, donde pasa datos vacíos *. los datos son un elemento perfectamente válido, puede calcular con él o, si tiene algún registro implementado, puede iniciar sesión.

logger << data;

y obtendrás los puntos de datos de dirección. Si intenta desreferenciar datos, el compilador le dará un error (no tiene a mano el compilador C ++ en este momento, por lo que no está seguro del error real). p.ej

void* data = /* some assignment */; logger << *data; // compiler error.

Ahora, el compilador no le permitirá desreferenciar un vacío * por ningún motivo (no tiene sentido), lo mismo significa una referencia a vacío y datos, excepto porque como referencia está desreferenciada implícitamente todo el tiempo . El compilador no le permitirá desreferenciar un vacío * en una operación, no le permitirá renunciar a ella constantemente.

void& data = /* some assignment *.; logger << data; // means same as logger << *data above

No se puede hacer NADA con los datos, EXCEPTO tomar su dirección, y hay un método perfectamente bueno y seguro integrado en el lenguaje para hacerlo, es decir,

void* data;

¿Esto tiene más sentido?


Puede pensar en una referencia como puntero desreferenciado. Sintácticamente trata una referencia como si no fuera un puntero: no necesita el operador * para desreferenciarla y puede usarla. en lugar de -> para acceder a sus miembros.

Sin embargo, no puede desreferenciar un puntero de void . Como lo señaló Binary Worrier al intentar hacer eso, obtendrá un error de compilación. Y si no puede tener un puntero nulo desreferenciado, eso significa que no puede tener una referencia vacía.


Si lo fueran, serían semánticamente no diferenciados de los indicadores, y equivaldrían a azúcar sintáctico. Una referencia dice: "Me refiero a algo que es de este tipo". Permitir referencias nulas o nulas debilitaría esa diferencia de los punteros.

Por supuesto, aún es posible que una referencia haga referencia a un objeto que ya no existe, pero eso es una excepción.


Si tuvieras una referencia a vacío, ¿qué harías con eso? No sería un número, ni un personaje, ni un puntero, ni nada de eso. Su función genérica hipotética no podría realizar ninguna operación en ella, excepto tomar su dirección (y no su tamaño).

"void" tiene dos usos: negar cualquier conocimiento de tipo (como en void *), y especificar nada en lugar de algo (return de función void). En ninguno de los casos es posible decir algo sobre un vacío, excepto que puede tener una dirección.

Si no puede pensar en la forma en que algo puede ser útil, y no puedo, eso es al menos evidencia de que algo es inútil, y eso bien puede ser al menos parte de la justificación aquí.


Técnicamente hablando, todo lo que está garantizado es que una referencia a un objeto es un alias para él. Que bajo el capó el argumento de referencia que pasa se hace con punteros es un detalle de implementación. Esto puede ser confuso debido a las referencias que reutilizan el operador & que también es dirección, pero tenga en cuenta que el operador realmente tiene diferentes significados en diferentes contextos (en una declaración de variable o parámetro denota un tipo de referencia, de lo contrario es la dirección de , excepto cuando es bitwise-and). Debido a que técnicamente es solo un alias para un objeto, una referencia es ''siempre desreferenciada'' como explicó Worrier.


Una referencia es una referencia a una instancia de algo. Una instancia de algo no puede ser de tipo void . Cualquier instancia de algo debe tener un tipo específico (y posiblemente tipos de base).


el vacío es algo que, por definición, no existe, por lo que no es lógico tener su dirección.


Lo siguiente no es una defensa de la noción de referencias vacías. Lo ofrezco como una anécdota de la naturaleza. Pregúntate si no huele raro.

Mi compañía fue una de las primeras que usó C ++ comercialmente, y compiló inicialmente usando Cfront . Los primeros desarrolladores todavía estaban aprendiendo el lenguaje y, en general, utilizando todos los trucos del libro (¡operadores en todas partes!). Aquí hay un truco que les pareció genial:

void Foo::something(int action, ostream &os = *(ostream *)0) { ostream *os_p = &os; if (&os == (ostream *)0) { os_p = &cerr; } // continue with method }

¡Aquí tienes, no una referencia vacía, sino una referencia tipada con un enlace potencialmente vacío ! Un momento de reflexión probablemente debería sugerir mejores alternativas a este idioma en particular ...


OK, una cosa me está molestando sobre esto. La idea de un void* , como se mencionó anteriormente, es que todavía tiene una variable válida que contiene una dirección, pero se está ignorando el tipo. Esto parece permisible ya que aún podemos trabajar con los datos de dirección; el tipo es algo superfluo (o menos importante) en este contexto. La desreferenciación es mala, porque intentar y acceder a un miembro no tiene sentido, por ejemplo, p.mem . No sabemos a qué clase referirse, y por lo tanto la memoria para saltar a los punteros vtable a seguir.

Sin embargo, parece lógico que p por sí solo esté bien, ya que solo se refiere al objeto, pero ninguno de sus datos. No se necesita información de clase para hacerlo, solo la dirección. Entiendo que no hay absolutamente ningún uso para esto, pero es importante para definir cuándo se rompen las cosas. Permitiendo esta noción, una referencia de C ++ (constantemente desreferenciada pero no accediendo a nada) por ejemplo, void& ref = static_cast< &void >(obj) también tiene sentido, y así permitiría referencias vacías. No digo que nadie deba abordarlo con los que están a cargo, pero desde el punto de vista de "hacer sentido", parece correcto, ¿no?

Como señaló Luc Touraille arriba (al menos, esta es mi interpretación), podría implementarse, pero el problema es semántico. La explicación razonable a la que pude llegar fue que, dado que una variable de objeto es una "etiqueta" para una secuencia de memoria, el tipo tiene un valor semántico importante. Por lo tanto, el puntero, al ser considerado como una variable con un valor de dirección, trata el tipo como algo superfluo, no clave para definirlo.

¿Alguien estaría de acuerdo con eso?