"Usando espacio de nombres" en los encabezados de C ++

119

En todos nuestros cursos de C ++, todos los profesores siempre colocan using namespace std;justo después de la #includes en sus .harchivos. Esto me parece peligroso, ya que al incluir ese encabezado en otro programa, obtendré el espacio de nombres importado a mi programa, tal vez sin darme cuenta, tener la intención o quererlo (la inclusión del encabezado puede estar muy profundamente anidada).

Entonces, mi pregunta es doble: ¿tengo razón en que using namespaceno debería usarse en archivos de encabezado, y / o hay alguna forma de deshacerlo, algo como:

//header.h
using namespace std {
.
.
.
}

Una pregunta más en la misma línea: ¿Debería un encabezado archivar #includetodos los encabezados que .cppnecesita su archivo correspondiente , solo aquellos que son necesarios para las definiciones de encabezado y dejar el .cpparchivo #includeel resto, o ninguno y declarar todo lo que necesita extern?
El razonamiento detrás de la pregunta es el mismo que el anterior: no quiero sorpresas al incluir .harchivos.

Además, si estoy en lo cierto, ¿es este un error común? Me refiero a la programación del mundo real y a los proyectos "reales" que existen.

Gracias.

Baruch
fuente
3
Como nota al margen, si obtiene colisiones de nombres debido a using namespacedeclaraciones, puede usar el nombre completo para resolver el problema.
Marius Bancila

Respuestas:

115

Definitivamente NO debe usar using namespaceen encabezados precisamente por la razón que dice, que puede cambiar inesperadamente el significado del código en cualquier otro archivo que incluya ese encabezado. No hay forma de deshacer una, using namespaceque es otra razón por la que es tan peligrosa. Por lo general, solo uso grepo similar para asegurarme de que using namespaceno se llame en los encabezados en lugar de intentar algo más complicado. Probablemente los verificadores de código estático también señalan esto.

El encabezado debe incluir solo los encabezados que necesita compilar. Una manera fácil de hacer cumplir esto es incluir siempre el encabezado de cada archivo fuente como lo primero, antes que cualquier otro encabezado. Entonces, el archivo de origen no se podrá compilar si el encabezado no es autónomo. En algunos casos, por ejemplo, refiriéndose a las clases de detalles de implementación dentro de una biblioteca, puede usar declaraciones hacia adelante en lugar de #includeporque tiene control total sobre la definición de dicha clase declarada hacia adelante.

No estoy seguro de si lo llamaría común, pero definitivamente aparece de vez en cuando, generalmente escrito por nuevos programadores que no son conscientes de las consecuencias negativas. Por lo general, solo un poco de educación sobre los riesgos soluciona cualquier problema, ya que es relativamente fácil de solucionar.

Marca B
fuente
2
¿Somos libres de utilizar usingdeclaraciones en nuestros .cpparchivos? las 3rdPartyLib::BigClassName<3rdPartyLib::AnotherBigName,3rdPartyLib::AnotherBigName>::Iterators son la muerte hasta la punta de los dedos.
Christopher
1
y ¿cómo deberíamos optimizar las templatefunciones, que se supone que están en los encabezados? typedefs?
Christopher
1
@donlan, parece que no recibió respuesta durante bastante tiempo ... Sí, puede usar usingdeclaraciones dentro de .cpparchivos sin mucha preocupación porque el alcance se limitará solo a ese archivo, pero nunca lo haga antes de una #includedeclaración. En cuanto a las funciones de plantilla definidas en los encabezados, desafortunadamente no conozco una buena solución que no sea simplemente escribir el espacio de nombres ... Quizás podría poner una usingdeclaración dentro de un alcance separado { /* using statement in between brackets */ }, que al menos evitaría que escape del archivo actual .
tjwrona1992
26

Elemento 59 en Sutter y Alexandrescu "Estándares de codificación C ++: 101 reglas, directrices y mejores prácticas" :

59. No escriba usos de espacio de nombres en un archivo de encabezado o antes de un #include.

Los espacios de nombres usingson para su conveniencia, no para que usted los imponga a otros: nunca escriba una usingdeclaración o una usingdirectiva antes de una #includedirectiva.

Corolario: en los archivos de encabezado, no escriba usingdirectivas o usingdeclaraciones a nivel de espacio de nombres ; en su lugar, explícitamente califica todos los nombres por espacio de nombres.

Un archivo de encabezado es un invitado en uno o más archivos de origen. Un archivo de encabezado que incluye usingdirectivas y declaraciones también trae a sus amigos ruidosos.

Una using declaración trae a un amigo. Una using directiva trae a todos los amigos en el espacio de nombres. El uso de sus profesores de using namespace std;es una directiva using.

Más en serio, tenemos espacios de nombres para evitar conflictos de nombres. Un archivo de encabezado está destinado a proporcionar una interfaz. La mayoría de los encabezados son independientes de qué código puede incluirlos, ahora o en el futuro. Agregar usingdeclaraciones para conveniencia interna dentro del encabezado coloca esos nombres convenientes en todos los clientes potenciales de ese encabezado. Eso puede llevar a un conflicto de nombres. Y es simplemente de mala educación.

Andy Thomas
fuente
12

Debe tener cuidado al incluir encabezados dentro de los encabezados. En proyectos grandes, puede crear una cadena de dependencia muy enredada que desencadena reconstrucciones más grandes / más largas de las que realmente eran necesarias. Consulte este artículo y su seguimiento para obtener más información sobre la importancia de una buena estructura física en los proyectos de C ++.

Solo debe incluir encabezados dentro de un encabezado cuando sea absolutamente necesario (siempre que se necesite la definición completa de una clase), y use la declaración de avance siempre que pueda (cuando la clase sea necesaria es un puntero o una referencia).

En cuanto a los espacios de nombres, tiendo a usar el ámbito explícito del espacio de nombres en mis archivos de encabezado, y solo pongo un using namespaceen mis archivos cpp.

Mike O'Connor
fuente
1
¿Cómo se optimiza la templatedeclaración de funciones? eso tiene que ocurrir en el encabezado, ¿no?
Christopher
6

Consulte los estándares de codificación del Goddard Space Flight Center (para C y C ++). Eso resulta ser un poco más difícil de lo que solía ser; vea las respuestas actualizadas a las preguntas de SO:

El estándar de codificación GSFC C ++ dice:

§3.3.7 Cada archivo de encabezado deberá incluir #includelos archivos que necesita compilar, en lugar de obligar a los usuarios a acceder a #includelos archivos necesarios. #includesse limitará a lo que necesite el encabezado; otros #includesdeben colocarse en el archivo fuente.

La primera de las preguntas con referencias cruzadas ahora incluye una cita del estándar de codificación GSFC C, y la justificación, pero la sustancia termina siendo la misma.

Jonathan Leffler
fuente
5

Tienes razón using namespaceen que en la cabecera es peligroso. No sé cómo deshacerlo. Es fácil de detectar, sin embargo, solo busque using namespaceen los archivos de encabezado. Por esa última razón es poco común en proyectos reales. Los compañeros de trabajo más experimentados pronto se quejarán si alguien hace algo así.

En proyectos reales, la gente intenta minimizar la cantidad de archivos incluidos, porque cuanto menos incluya, más rápido se compilará. Eso ahorra tiempo a todos. Sin embargo, si el archivo de encabezado asume que algo debe incluirse antes, entonces debe incluirlo él mismo. De lo contrario, hace que los encabezados no sean independientes.

Öö Tiib
fuente
4

Tienes razón. Y cualquier archivo solo debe incluir los encabezados necesarios para ese archivo. En cuanto a "¿es común hacer las cosas mal en los proyectos del mundo real?" - ¡Oh si!


fuente
4

Como todas las cosas en la programación, el pragmatismo debería vencer al dogmatismo, en mi opinión.

Siempre que tome la decisión en todo el proyecto ("Nuestro proyecto usa STL extensamente y no queremos tener que anteponer todo con std ::."), No veo el problema con eso. Después de todo, lo único que corre el riesgo son las colisiones de nombres, y con la ubicuidad de STL es poco probable que sea un problema.

Por otro lado, si fue una decisión de un desarrollador en un solo archivo de encabezado (no privado), puedo ver cómo generaría confusión entre el equipo y debería evitarse.

ijprest
fuente
4

Con respecto a "¿Hay alguna forma de deshacer [una usingdeclaración]?"

Creo que es útil señalar que las usingdeclaraciones se ven afectadas por el alcance.

#include <vector>

{   // begin a new scope with {
    using namespace std;
    vector myVector;  // std::vector is used
}   // end the scope with }

vector myOtherVector;   // error vector undefined
std::vector mySTDVector // no error std::vector is fully qualified

Así que efectivamente sí. Al limitar el alcance de la usingdeclaración, su efecto solo dura dentro de ese alcance; se 'deshace' cuando termina ese alcance.

Cuando la usingdeclaración se declara en un archivo fuera de cualquier otro ámbito, tiene ámbito de archivo y afecta a todo en ese archivo.

En el caso de un archivo de encabezado, si la usingdeclaración está en el alcance del archivo, esto se extenderá al alcance de cualquier archivo en el que esté incluido el encabezado.

YoungJohn
fuente
2
parece que eres el único en entender la pregunta real ... sin embargo, mi compilación no está muy contenta de que use la desaceleración dentro de la clase.
papel oxidado
Esta respuesta podría mejorarse aún más explicando el problema con la idea del OP de cómo debería funcionar el alcance (como la namespacedeclaración) frente a cómo funciona realmente (como una variable). {}encerrarlo limita su alcance, {}después de que no hace nada relacionado con él. Esa es una forma accidental en la que using namespacese aplica globalmente.
Taft
2

Creo que puede usar 'using' en los encabezados de C ++ de manera segura si escribe sus declaraciones en un espacio de nombres anidado como este:

namespace DECLARATIONS_WITH_NAMESPACES_USED_INCLUDED
{
    /*using statements*/

    namespace DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED
    {
        /*declarations*/
    }
}

using namespace DECLARATIONS_WITH_NAMESPACES_USED_INCLUDED::DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED;

Esto debe incluir solo las cosas declaradas en 'DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED' sin los espacios de nombres utilizados. Lo he probado en el compilador mingw64.

AnArrayOfFunctions
fuente
Esta es una técnica útil que no había visto antes; Gracias. Normalmente he estado bien con el uso de la calificación de alcance completo y colocando usingdeclaraciones dentro de las definiciones de funciones donde puedo para que no contaminen los espacios de nombres fuera de la función. Pero ahora quiero usar literales definidos por el usuario de C ++ 11 en un archivo de encabezado, y según la convención habitual, los operadores literales están protegidos por un espacio de nombres; pero no quiero usarlos en listas de inicializadores de constructores que no están en un alcance en el que puedo usar una usingdeclaración no contaminante . Entonces esto es genial para resolver ese problema.
Anthony Hall
A pesar de que un efecto secundario desafortunado de este patrón es que las clases declaradas dentro del espacio de nombres más interna se mostrarán en los mensajes de error del compilador con el nombre completo: error: ... DECLARATIONS_WITH_NAMESPACES_USED_INCLUDED:: DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED::ClassName .... Al menos, eso es lo que me está sucediendo en g ++.
Anthony Hall