2009-06-04 24 views
5

Una pregunta muy básica, pero aún así, sería bueno saber de los gurús de C++ que hay.¿Cuáles son las diferencias entre las definiciones de parámetros como (tipo y nombre) y (tipo * nombre)?

Existen dos formas bastante similares de declarar parámetros de referencia por referencia en C++.

1) El uso de "asterisco":

void DoOne(std::wstring* iData); 

2) El uso de "signo":

void DoTwo(std::wstring& iData); 

¿Cuáles son las implicaciones de cada método? ¿Hay alguna gotcha en cualquier caso?

Bono n. ° 1: ¿Cuál sería una forma formal de llamar al método en los números 1 y 2? ¿Los dos son llamados "por referencia"?

Bonificación n. ° 2: std :: wstring se usa deliberadamente. ¿Cuáles serían las implicaciones para las clases de biblioteca estándar en cada caso?

+1

Se han dicho muchos aspectos sobre los argumentos de referencia/puntero. Eche un vistazo aquí también: http://stackoverflow.com/questions/57483/difference-between-pointer-variable-and-reference-variable-in-c. – xtofl

Respuesta

5

# 1 usa un parámetro de puntero ('pasando un puntero a'), el # 2 usa un parámetro de referencia ('pasando por referencia'). Son muy similares, pero tenga en cuenta que el código de llamada se ve diferente en los dos casos:

std::wstring s; 

DoOne(&s); // pass a pointer to s 
DoTwo(s); // pass s by reference 

Algunas personas prefieren # 1, utilizando una convención que pasa por el puntero indica que la función puede cambiar el valor de s (incluso aunque cualquiera de las funciones podría). Otras personas (yo incluido) prefieren el n. ° 2, ya que pasar por referencia no permite que se pase NULL.

Hay otra diferencia importante al pasar por const puntero o referencia. Una variable temporal sólo puede ser pasado a un parámetro de referencia constante:

void ByConstPointer(const std::wstring&); 
void ByConstReference(const std::wstring*); 

void test() 
{ 
    ByConstPointer(&std::wstring(L"Hello")); // error: cannot take address of temporary 
    ByConstReference(std::wstring(L"Hello")); // fine 
} 
+1

+1 para la variable temporal! –

1

Al escribir ejemplos, se me ocurrió mi propia respuesta. ¿Algo más que debajo?

El resultado de cada uno de ellos es bastante similar: una referencia a un objeto en la memoria termina dentro del alcance del método. Parece que no hay requisitos estrictos de memoria para ninguno de ellos. El objeto puede estar en pila o en montón.

En caso de pila de cada uno de los métodos se llamaría así:

{ 
    std::wstring data; 
    DoOne(&data); 
    DoTwo(data); 
} 

Sin embargo, cuando se trata de la pila, el segundo enfoque requeriría que el objeto debe existir antes de llamar al método. Si el objeto no existe, el que llama causaría una excepción, no el destinatario.

{ 
    std::wstring* pData = new std::wstring(); 
    DoOne(pData); 
    DoTwo(*pData); 
} 

En lo anterior, si fuera de la memoria y la condición se produce pData termina NULL, el accidente podría suceder antes de DoTwo, pero DOONE se tragaría el NULL y se puede bloquear algún tiempo después.

+0

+1 No hay mucho que agregar :) – ralphtheninja

+0

¡Hay! ¿Cómo se llama formalmente a cada método? –

+0

DoOne se pasa por referencia, DoTwo es pase por puntero. Las referencias no pueden ser NULL, el compilador las impone. Por lo tanto, su afirmación "Si el objeto no existe, la persona que llama causaría una excepción" es incorrecta. Además, new no puede devolver NULL. Si falla, arroja una excepción. Entonces no necesitas preocuparte. Pass-by-reference es preferido en la mayoría de los casos. – rlbond

0

No me llamaría una cifra de C++ (excepto en mi CV), pero diría; a menos que haya algún uso de pasar el parámetro como un puntero (es decir, si la función quiere verificar nulo), siempre use una referencia.

Eso también se aplica a las funciones que devuelven objetos, devolver un puntero de alguna manera le dice al usuario de la clase que podría ser nulo.

0

En DoOne, se puede asignar iData NULO. Si usa eso después de llamar a DoOne, la aplicación se bloqueará.

Algo así como

void DoOne(std::wstring* iData) 
{ 
    //Use iData 
    delete iData; 
    iData = NULL; 
} 

y

{ 
    std::wstring* pData = new std::wstring(); 
    DoOne(pData); 
    pData->someFunction(); //Crash 
} 
+0

Se bloqueará porque se eliminó el objeto, no porque el puntero se haya asignado a NULL. –

0

Su respuesta es completamente equivocado cuando dice:

El resultado de cada uno de ellos es bastante similar: una referencia a un objeto en la memoria termina dentro del alcance del método .No parece haber requisitos estrictos de memoria para ninguno de ellos.

connsider:

void f(int * p1) { 
    int ** p2 = & p1; 
} 

aquí P1 tiene un claro "requisito de memoria" - que debe existir y que debe ser capaz de tomar su dirección. Contraste esto con

void f(int & r) } 
    int * p = & r; 
} 

aquí r no tiene existencia propia, es meramente una referencia. Whem tomo su dirección que estoy tomando la dirección de lo que r se refiere.

Sus observaciones con respecto al puntero NULL también son erróneas. Al hacer descender el puntero NULL, se produce un comportamiento indefinido; esto puede provocar o no un bloqueo.

+0

¿Quiere decir que en C++ no existe el "Pase por el puntero"? Aunque tienes razón, creo que la pregunta es cuándo usar un puntero o una referencia. – xtofl

+0

No, no quiero decir eso. –

0

Si se escribe una función que recibe una variable puntero, muy probablemente tendrá que comprobar si el puntero es válido (por ejemplo, no es NULL) , de lo contrario corre el riesgo de colapsar el programa.

+0

"se arriesga a fallar el programa". Las funciones estándar como strcpy no comprueban null. Consideran que es su interlocutor el que se arriesga a sufrir un bloqueo (al pasar un valor no válido), no a ellos. –

2

Regla número uno para esto: Si NULL es un valor válido para el parámetro de función en el contexto de la función, páselo como puntero; de lo contrario, páselo como referencia.

Justificación, si no puede (¡no debería!) Ser NULO, entonces no se tome la molestia de buscar NULL.

Cuestiones relacionadas