2010-03-16 14 views
22
bool is_something_ok(int param,SomeStruct* p) 
{ 
    bool is_ok = false; 

    // check if is_ok 

    if(is_ok) 
     // set p to some valid value 
    else 
     // set p to NULL 
    return is_ok; 
} 

esta función de retorno verdadera y conjunto P a un valor válido si "algo está mal" en caso contrario devuelve falso y establecer en nulo p¿Es un buen diseño para devolver el valor por parámetro?

¿Es que un diseño bueno o malo? personalmente, me siento incómodo cuando lo uso. Si no hay ningún documento y comentario, realmente no sé cómo usarlo.

BTW: ¿Hay algún libro/artículo autorizado sobre diseño de API?

+7

Tenga en cuenta que la función no puede establecer el puntero a NULL. Una copia del puntero se pasa como parámetro, e incluso si esa copia se hace nula, el código de llamada no se dará cuenta. Si quieres jugar con el puntero, necesitas un nivel adicional de direccionamiento indirecto (llámalo puntero o referencia: 'bool op (int, SomeStruct * &)' o 'bool op (int, SomeStruct **)' y entonces las cosas se ponen en un poco más desagradable. –

+0

Es posible que desee ver esta respuesta: http://stackoverflow.com/questions/2139224/2139254#2139254 – sbi

Respuesta

12

Puesto que usted ha marcado la pregunta como C++ y no en C, que sería en que sugeriría:

  • retorno el valor directamente
  • si usted tiene más de un valor, utilice los parámetros de salida
  • use referencias no constantes como parámetro de salida cuando sea posible (en lugar de punteros), y use const-references para los parámetros de entrada.
  • si algo salió mal, genera una excepción en lugar de devolver falso o -1.

Pero solo son algunos consejos generales. La mejor manera de proceder siempre depende del problema específico ...

1

pienso volver NULL si algo no está bien y SomeStruct válida si bien es mejor en este caso

SomeStruct* is_something_ok(int param); 

En este caso, a excepción de comprobación de valor booleano que debe comprobar es nulo, y si no uso eso.

Pero hay casos en los que tiene que devolver el valor por parámetro. Depende del número de valores devueltos, y se puede usar la función de contexto.

+0

Pero a veces NULL * es * un valor de retorno válido –

+0

@Matthew En ese caso definiría un implementación particular de su clase para que sea representativa de un valor nulo. Entonces, el código de llamada puede manejar ambas situaciones indistintamente. –

+1

Lo que complica el diseño, si trabaja en equipo, puede ser mejor mantener las cosas simples. – Phong

1

Usted puede hacer lo siguiente:

bool is_something_ok(int param,SomeStruct* p); 

o

// return NULL if the operation failed. 
Somestruct* do_work(int param); 

Es difícil decir si un diseño/API es bueno o malo, nada es blanco o negro ... (gris ?!?!?)

Debe elegir la API/Estándar que será más fácil para que codifique con. Y sea coherente, si elige el primer tipo de método, hágalo para el resto de su proyecto.

No olvide también para documentar su código, por lo que será más fácil entender cómo usar su API.

2

Tiendo a hacer esto. La alternativa en su ejemplo es codificar dos cosas en un valor de retorno (por ejemplo, usando NULL como un valor especial) o devolver una estructura.

La codificación de dos cosas a veces puede ser imposible, y es un poco propenso a errores. Devolver una estructura es mucho trabajo adicional y desorden. Así que tiendo a hacer lo que has hecho. Tiendo a suponer que los punteros "en bruto" y las referencias en una lista de parámetros son para devolver los valores, y serían "const" si fueran solo para pasar datos en.

Pero para ser honesto, olvido esa regla como a menudo como lo recuerdo, así que tal vez no sea muy bueno.

Hay una clase "opcional" en la biblioteca de impulso que puede ajustarse a sus necesidades, pero nunca me gustó, quizás por alguna razón no muy buena.

+0

+1 para llevar opcional a La discusión. Una pequeña utilidad sofisticada que casi siempre se olvida ... –

3

Depende de cómo quiera manejar los "errores".

Por ejemplo, tome la función estándar atoi. Convierte una cadena en un entero, pero si la cadena no contiene un número, ¿qué debería devolver? En este caso, el tiempo de ejecución de C/C++ configurará la variable global errno. Una alternativa sería arrojar una excepción.

Personalmente, realmente no me gustan estas dos alternativas. Por lo tanto, si en general, utilizar las siguientes reglas:

  • ¿Hay un valor en el rango de posibles valores de retorno que se pueden utilizar para indicar un error, y no sólo tengo 1 posibilidad 'especie de' error? En esos casos, uso el valor de retorno para indicar el error. P.ej. una función como FindEmployee podría simplemente devolver NULL si no se encuentra el empleado.
  • Si la función puede devolver todos los valores posibles (como en el ejemplo atoi), use un argumento de salida para el valor de retorno, y deje que la función devuelva un valor booleano. Si tiene más de 1 caso de error posible, devuelva una enumeración que indique el tipo de error (o éxito) que ocurrió.
+0

La tercera opción (que se usa en QT) está pasando una variable de retorno de error como argumento ('int QString :: toInt (bool * error = 0, int base = 0)') +1 para traer otros mecanismos de error a discusión. –

2

Yo diría que depende. ¿Qué tan caro es tu tipo para copiar la construcción? ¿Puedes escribir tu función RVO -friendly? Al menos hasta que tengamos C++ 0x con rvalue references, mi recomendación sería no devolver tipos "costosos" (como std::vector<std::string>), sino pasarlos como referencias, p. utilizar:

void split(const std::string &txt, char sep, std::vector<std::string> &out); 

en lugar de:

std::vector<std::string> split(const std::string &txt, char sep); 

Dependiendo de cómo se escribe su función es posible que RVO entrará en funcionamiento pero en mi experiencia no es algo que por lo general se puede confiar.

1

sugeriría a devolver el del Resultado de tipo directamente así:

SomeStruct doSomething(int param) {...} 

y lanzar una excepción en los casos en que la función no puede manejar (tux21b ya se ha dicho de esta manera). Como alternativa, puede devolver dos tipos con std::pair sin lanzar una excepción de este modo:

pair<SomeStruct, bool> doSomething(int param) {...} 

Y en tercer lugar me gusta declarar parámetros de salida como punteros en lugar de referencias (como usted ha mencionado), ya que en el código de llamada que veo la diferencia de parámetros de entrada y salida.Dada la función:

void doSomething(const Somestruct& in, Somestruct* out) {...} 

Luego, en el código de llamada que es visible (sin tener en cuenta la declaración de función) ¿cuál es la entrada y cuál es el parámetro de salida (si consecently aplicar este concepto).

SomeStruct a; 
SomeStruct b; 
doSomething(a, &b); // you see a is input, b is output 
2

En relación con su pregunta sobre un libro sobre diseño de API. Busque "API Design for C++" por Martin Reddy, que se publicó en 2011.

Como comentario a la respuesta aceptada. En el libro, el autor en realidad sugiere preferir las referencias de referencia para los parámetros de entrada y los indicadores para los parámetros de salida, ya que indica al cliente de manera más explícita que el parámetro puede modificarse, p. foo (bar) vs. foo (& bar).

También puede ver la conversación How To Design A Good API and Why it Matters. Que usa principalmente Java sin embargo, como recuerdo.

Cuestiones relacionadas