2012-03-15 6 views
9
código

La siguiente C++ no se compila:¿Por qué iostream define una función abs y cómo puedo detenerla?

int main() { 
    double a = abs(5.1); 
    return 0; 
} 

Se queja de que abs no está definido, por supuesto. Pero el siguiente compila:

#include <iostream> 

int main() { 
    std::cout << abs(5.1) << std::endl; 
    std::cout << abs(-5.1) << std::endl; 
    return 0; 
} 

Emite dos 5 (no 5.1). Esto es malo por muchas razones. En primer lugar, abs es una función tan natural y común que la utilizo todo el tiempo, pero la parte int casi nunca es lo que deseo devolver. En segundo lugar, es demasiado fácil para mí (o para las personas que usan mi código) escribir abs y no darme cuenta de que compila, pero hace lo incorrecto, porque soy (son) realmente buenos para pasar por alto las advertencias. En tercer lugar, simplemente no entiendo por qué iostream molesta la definición de una función abs de todos modos. En cuarto lugar, I realmente no entiendo por qué entra en el espacio de nombres global.

¿Hay alguna manera de evitar que esta objetable función abs entre en mi espacio de nombres global?

si importa, estoy usando

gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.6) 
+4

Esto es probablemente porque '' es '# include'-ing' 'detrás de escena. –

+3

Esto no debería compilarse. No debería haber una función 'abs' en el espacio de nombres global cuando se incluye' '(no se compila en g ++ 4.6.2 y http://ideone.com/QP6cj). – Zeta

+1

@Zeta - it * does * compila e importa 'abs' al espacio de nombres global utilizando el compilador y el sistema operativo que tiene, lo probé para estar seguro. Ese es el Xcode actual de Apple, por lo que la actualización no es sencilla. –

Respuesta

11

Lo más probable es iostream incluye stdlib.h hacer parte de su trabajo. Esta es la versión C del encabezado que declara abs para int solo en el espacio de nombres global (en C tenía que usar fabs para los valores double).

No estoy al tanto de ninguna manera específica para mantener abs se incluya esa manera, pero sí sé que g ++ 4.5 es mucho mejor en no tener exceso de material traído por básica incluye como iostream y string.

También es posible recibir una advertencia de que el doble está siendo truncado a int (EDITAR: sí, use -Wconversion para advertir).

+0

Bueno, yo sabía sobre la advertencia, pero, como mencioné en la pregunta, haré que los usuarios amplíen mi código, y estoy seguro de que apagarán las advertencias o simplemente las ignorarán. Y luego, este pequeño y pernicioso problema de "abs" se deslizará y dará silenciosamente resultados incorrectos. Ooh, este tipo de cosas me molesta! De todos modos, gracias por señalar la causa. – Mike

+2

@Mike: cuando dices, "este tipo de cosas me molestan", quieres decir que te molesta que ignorar las advertencias genere un código erróneo, ¿verdad? ;-p –

+0

Bueno, supongo que ambos me molestan. Pero siendo la naturaleza humana lo que es, trato de diseñar para la prueba de idiotez. Cosas como esta silenciosa inclusión de 'abdominales' son todo lo contrario: son trampas idiotas secretamente ocultas. Están tan ingeniosamente ocultos que el idiota puede que nunca sepa que estaba atrapado. Y para colmo, ¡este me impide activamente a prueba de idiotas! Entonces esto me molesta más. – Mike

4

En C, incluido un encabezado estándar fue no permitido actuar como si incluyera cualquier otro encabezado estándar. Esto evitó el problema que está viendo, a un costo considerable en la implementación de la dificultad.

C++ permite que cualquier encabezado estándar incluya cualquier otro encabezado estándar. Esto hace que la implementación sea considerablemente más fácil, pero puede llevar exactamente al tipo de problema que está viendo, donde incluir un encabezado aparentemente no relacionado ha hecho visible una función que realmente no quería usar, en lugar de obtener un error porque la función Usaste no está declarado en absoluto.

Desafortunadamente, no creo que haya una manera fácil de lidiar con esto. Aunque es bastante fácil imaginar que <iostream> sea independiente de <stdlib.h>, es mucho más fácil ver cómo podría necesitar/querer definiciones de cosas como ios_base. Tomaría bastante trabajo extra definir las cosas para prohibir lo primero mientras se permite lo último.

Debo mencionar, sin embargo, que con el tiempo esta situación parece estar mejorando bastante. Hace diez años, era bastante común que virtualmente todos los encabezados estándar incluyeran casi a cualquiera de ellos. Aunque la mayoría aún incluye al menos algunos que no son estrictamente necesarios, en general están mucho más cerca de que cada uno defina solo lo que se requiere.

+1

Si nada más, puedes esperar que 'iostream' incluya' cstdlib' en lugar de 'stdlib.h', y que' cstdlib' no incluya 'stdlib.h' en absoluto, o bien lo haga dentro del espacio de nombres' std'. Pero como dices, eso no está garantizado, y manifiestamente no es lo que hace GCC 4.2. –

+1

@SteveJessop: Podrías esperar, pero entonces muchos compiladores contaminaron el espacio de nombres global cuando incluiste la variante '', que en C++ 11, la restricción en contra de hacerlo ha sido eliminada ... –

1

Si se trata de un problema de mantenimiento continuo, ¿por qué no agregar un pequeño código al comienzo del programa que comprueba específicamente el problema abs()?

// test if abs() is defined incorrectly, as would happen if <stdlib.h> were 
// included by <iostream>. abs() it should return a float/double, not an int 
// (put suggestions here how to fix problem) 
if (abs(-5.1) == 5) 
{ 
    std::cerr << "Invalid build: abs() defined improperly, ..." << std::endl; 
    return 2; // exit program by returning from main 
} 

Eso hará que sea mucho más difícil que las advertencias sean pasadas por alto.

+0

Idea interesante. Es bastante feo, pero creo que realmente lo haría, excepto que gran parte de mi código se usa como biblioteca. Por lo tanto, no está claro dónde pondré esa prueba y la ejecutaré en realidad, al menos no sin hacer que esta solución sea mucho más fea. – Mike

+0

@Mike: el lugar obvio sería cualquier código que siempre se llame, como la inicialización de la biblioteca. ¿La base de código usa 'ASSERT'? ¿Y/o tener un modo 'DEBUG'? Cualquiera de estos son compatibles con una prueba de este tipo. – wallyk

+0

No, no hay tal lugar. Es una biblioteca muy modular y no necesita inicialización. Lo que es peor es que la corrección de un 'abs' particular depende de si se usan o no declaraciones como' using namespace std; '. En ese caso, esta prueba probablemente tendría que ocurrir en cada unidad de compilación, incluida la del usuario, cuando corresponda. – Mike

Cuestiones relacionadas