¿Razón para pasar un puntero por referencia en C ++?

97

¿En qué circunstancias le gustaría utilizar código de esta naturaleza en c ++?

void foo(type *&in) {...}

void fii() {
  type *choochoo;
  ...
  foo(choochoo);
}
Matthew Hoggan
fuente
si necesita devolver un puntero, mejor use un valor de retorno
littleadv
6
¿Puedes explicar por qué? Este elogio no es muy útil. Mi pregunta es bastante legítima. Esto se está utilizando actualmente en código de producción. Simplemente no entiendo completamente por qué.
Matthew Hoggan
1
David lo resume bastante bien. El puntero en sí se está modificando.
chris
2
Paso de esta manera si voy a llamar al operador "Nuevo" en la función.
NDEthos

Respuestas:

142

Debería pasar un puntero por referencia si necesita modificar el puntero en lugar del objeto al que apunta el puntero.

Esto es similar a por qué se utilizan punteros dobles; usar una referencia a un puntero es un poco más seguro que usar punteros.

David Z.
fuente
14
¿Tan similar a un puntero de un puntero, excepto un nivel menos de indirección para la semántica de paso por referencia?
Me gustaría agregar que a veces también desea devolver un puntero por referencia. Por ejemplo, si tiene una clase que contiene datos en una matriz asignada dinámicamente, pero desea proporcionar acceso (no constante) a estos datos al cliente. Al mismo tiempo, no desea que el cliente pueda manipular la memoria a través del puntero.
2
@William, ¿puedes dar más detalles sobre eso? Suena interesante. ¿Significa que el usuario de esa clase podría obtener un puntero al búfer de datos interno y leer / escribir en él, pero no puede, por ejemplo, liberar ese búfer usando deleteo volver a enlazar ese puntero a algún otro lugar de la memoria? ¿O me equivoqué?
BarbaraKwarc
2
@BarbaraKwarc Seguro. Supongamos que tiene una implementación simple de una matriz dinámica. Naturalmente, sobrecargará al []operador. Una posible firma (y de hecho natural) para esto sería const T &operator[](size_t index) const. Pero también podrías haberlo hecho T &operator[](size_t index). Podrías tener ambos al mismo tiempo. Este último te dejaría hacer cosas como myArray[jj] = 42. Al mismo tiempo, no está proporcionando un puntero a sus datos, por lo que la persona que llama no puede alterar la memoria (por ejemplo, eliminarla accidentalmente).
Oh. Pensé que estabas hablando de devolver una referencia a un puntero (tu redacción exacta: "devolver un puntero por referencia", porque eso era lo que preguntaba OP: pasar punteros por referencias, no solo referencias antiguas) como una forma elegante de dando acceso de lectura / escritura al búfer sin permitir manipular el búfer en sí (por ejemplo, liberarlo). Devolver una simple referencia antigua es algo diferente y lo sé.
BarbaraKwarc
65

Al 50% de los programadores de C ++ les gusta establecer sus punteros en nulos después de una eliminación:

template<typename T>    
void moronic_delete(T*& p)
{
    delete p;
    p = nullptr;
}

Sin la referencia, solo estaría cambiando una copia local del puntero, sin afectar a la persona que llama.

fredoverflow
fuente
19
Me gusta el nombre ; )
4pie0
2
Cambiaré el sonido estúpido por descubrir la respuesta: ¿por qué se llama eliminación idiota? ¿Qué me estoy perdiendo?
Sammaron
1
@Sammaron Si miras el historial, la plantilla de la función solía ser llamada paranoid_delete, pero Puppy la renombró moronic_delete. Probablemente pertenezca al otro 50%;) De todos modos, la respuesta corta es que la configuración de punteros a null after deletecasi nunca es útil (porque deberían salir del alcance, de todos modos) y a menudo dificulta la detección de errores "use after free".
fredoverflow
@fredoverflow Entiendo tu respuesta, pero @Puppy parece pensar que deletees estúpido en general. Puppy, busqué en Google, no veo por qué eliminar es completamente inútil (tal vez yo también sea un buscador terrible de Google;)). ¿Puede explicar un poco más o proporcionar un enlace?
Sammaron
@Sammaron, C ++ tiene "punteros inteligentes" ahora que encapsulan punteros regulares y automáticamente llaman a "eliminar" cuando se salen de su alcance. Los punteros inteligentes son ampliamente preferidos a los punteros tontos de estilo C porque eliminan este problema por completo.
tjwrona1992
14

La respuesta de David es correcta, pero si aún es un poco abstracta, aquí hay dos ejemplos:

  1. Es posible que desee poner a cero todos los punteros liberados para detectar problemas de memoria antes. Estilo C que harías:

    void freeAndZero(void** ptr)
    {
        free(*ptr);
        *ptr = 0;
    }
    
    void* ptr = malloc(...);
    
    ...
    
    freeAndZero(&ptr);

    En C ++ para hacer lo mismo, puede hacer:

    template<class T> void freeAndZero(T* &ptr)
    {
        delete ptr;
        ptr = 0;
    }
    
    int* ptr = new int;
    
    ...
    
    freeAndZero(ptr);
  2. Cuando se trata de listas vinculadas, a menudo se representan simplemente como punteros a un nodo siguiente:

    struct Node
    {
        value_t value;
        Node* next;
    };

    En este caso, cuando inserta en la lista vacía, necesariamente debe cambiar el puntero entrante porque el resultado ya no es el NULLpuntero. Este es un caso en el que modifica un puntero externo de una función, por lo que tendría una referencia al puntero en su firma:

    void insert(Node* &list)
    {
        ...
        if(!list) list = new Node(...);
        ...
    }

Hay un ejemplo en esta pregunta .

Antoine
fuente
8

Tuve que usar un código como este para proporcionar funciones para asignar memoria a un puntero pasado y devolver su tamaño porque mi empresa me "objeta" usando el STL

 int iSizeOfArray(int* &piArray) {
    piArray = new int[iNumberOfElements];
    ...
    return iNumberOfElements;
 }

No es agradable, pero el puntero debe pasarse por referencia (o usar un puntero doble). De lo contrario, la memoria se asigna a una copia local del puntero si se pasa por valor, lo que da como resultado una pérdida de memoria.

matemático 1975
fuente
2

Un ejemplo es cuando escribe una función de analizador y le pasa un puntero de origen para leer, si se supone que la función empuja ese puntero hacia adelante detrás del último carácter que ha sido reconocido correctamente por el analizador. El uso de una referencia a un puntero deja en claro que la función moverá el puntero original para actualizar su posición.

En general, usa referencias a punteros si desea pasar un puntero a una función y dejar que mueva ese puntero original a otra posición en lugar de simplemente mover una copia sin afectar el original.

BarbaraKwarc
fuente
¿Por qué el voto negativo? ¿Hay algún problema con lo que escribí? ¿Le importaria explicar? : P
BarbaraKwarc
0

Otra situación en la que puede necesitar esto es si tiene una colección stl de punteros y desea cambiarlos usando el algoritmo stl. Ejemplo de for_each en c ++ 98.

struct Storage {
  typedef std::list<Object*> ObjectList;
  ObjectList objects;

  void change() {
    typedef void (*ChangeFunctionType)(Object*&);
    std::for_each<ObjectList::iterator, ChangeFunctionType>
                 (objects.begin(), objects.end(), &Storage::changeObject);
  }

  static void changeObject(Object*& item) {
    delete item;
    item = 0;
    if (someCondition) item = new Object();
  } 

};

De lo contrario, si usa la firma changeObject (Object * item) , tiene una copia del puntero, no la original.

Nueve
fuente
esto es más un "objeto de reemplazo" que un "objeto de cambio" - hay una diferencia
serup