Espacio de nombres + funciones versus métodos estáticos en una clase

290

Digamos que tengo, o voy a escribir, un conjunto de funciones relacionadas. Digamos que están relacionadas con las matemáticas. Organizacionalmente, debería:

  1. Escribe estas funciones y ponlas en mi MyMath espacio de nombres y consúltelas a través deMyMath::XYZ()
  2. Cree una clase llamada MyMathy haga que estos métodos sean estáticos y consulte el mismoMyMath::XYZ()

¿Por qué elegiría uno sobre el otro para organizar mi software?

RobertL
fuente
44
Por un lado, los espacios de nombres son una adición más reciente al lenguaje, en comparación con las clases y los métodos estáticos, que estaban en el lenguaje desde el momento en que se llamaba "C con clases". Algunos programadores pueden sentirse más cómodos con las funciones más antiguas. Algunos otros programadores pueden estar usando compiladores antiguos. Just my $ .02
Rom
21
@Rom: Tienes razón acerca de los "viejos programadores", pero estás equivocado acerca de los "viejos compiladores". Los espacios de nombres se compilan correctamente desde hace eones (¡trabajé con ellos con Visual C ++ 6, que data de 1998!). En cuanto a la "C con clases", algunas personas en este foro ni siquiera nacieron cuando eso sucedió: usar esto como un argumento para evitar una característica estándar y generalizada de C ++ es una falacia. En conclusión, solo los compiladores de C ++ obsoletos no admiten espacios de nombres. No uses ese argumento como una excusa para no usarlos.
paercebal
@paercebal: algunos compiladores antiguos todavía están en uso en el mundo integrado. No admitir espacios de nombres es probablemente uno de los inconvenientes más pequeños que uno debe soportar al escribir código para varias CPU pequeñas con las que todos interactúan todos los días: su estéreo, su microondas, la unidad de control del motor de su automóvil, el semáforo, etc. Sea claro: no estoy abogando por no usar compiladores mejores y más nuevos en todas partes. Au conrare: estoy a favor de las nuevas características del lenguaje (excepto RTTI;)). Solo estoy señalando que existe tal tendencia
Rom
13
@Rom: en el caso actual, el autor de la pregunta tiene la opción, por lo que aparentemente ninguno de sus compiladores falla al compilar un código de espacio de nombres. Y como se trata de una pregunta sobre C ++, se debe dar una respuesta de C ++, incluida la mención de espacios de nombres y soluciones RTTI al problema si es necesario. Dar una respuesta C o una respuesta C-con-clases-para-compiladores obsoletos está fuera de tema.
paercebal
2
"Just my $ .02": valdría más si hubiera proporcionado alguna evidencia de compiladores de C ++ existentes que no admitan espacios de nombres. "algunos compiladores antiguos todavía están en uso en el mundo incrustado" - esto es una tontería; El "mundo incrustado" es un desarrollo más reciente que los espacios de nombres C ++. "C con clases" se introdujo en 1979, mucho antes de que alguien incrustara el código C en algo. "Solo estoy señalando que existe esa tendencia", incluso si es así, eso no tiene nada que ver con esta pregunta.
Jim Balter

Respuestas:

243

Por defecto, use funciones de espacio de nombres.

Las clases son para construir objetos, no para reemplazar espacios de nombres.

En código orientado a objetos

Scott Meyers escribió un artículo completo para su libro eficaz de C ++ sobre este tema, "Prefiero funciones que no sean miembros y no amigos a funciones miembros". Encontré una referencia en línea a este principio en un artículo de Herb Sutter:http://www.gotw.ca/gotw/084.htm

Lo importante es saber que: en C ++, las funciones en el mismo espacio de nombres que una clase pertenecen a la interfaz de esa clase (porque ADL buscará esas funciones al resolver llamadas de función).

Las funciones de espacio de nombres, a menos que se declare "amigo", no tienen acceso a los elementos internos de la clase, mientras que los métodos estáticos sí.

Esto significa, por ejemplo, que al mantener su clase, si necesita cambiar los elementos internos de su clase, deberá buscar los efectos secundarios en todos sus métodos, incluidos los estáticos.

Extensión I

Agregar código a la interfaz de una clase.

En C #, puede agregar métodos a una clase incluso si no tiene acceso a ella. Pero en C ++, esto es imposible.

Pero, aún en C ++, aún puede agregar una función de espacio de nombres, incluso a una clase que alguien escribió para usted.

Vea desde el otro lado, esto es importante al diseñar su código, porque al poner sus funciones en un espacio de nombres, autorizará a sus usuarios a aumentar / completar la interfaz de la clase.

Extensión II

Como efecto secundario del punto anterior, es imposible declarar métodos estáticos en múltiples encabezados. Todos los métodos deben declararse en la misma clase.

Para espacios de nombres, las funciones del mismo espacio de nombres se pueden declarar en varios encabezados (la función de intercambio casi estándar es el mejor ejemplo de eso).

Extensión III

La frescura básica de un espacio de nombres es que en algún código, puede evitar mencionarlo, si usa la palabra clave "using":

#include <string>
#include <vector>

// Etc.
{
   using namespace std ;
   // Now, everything from std is accessible without qualification
   string s ; // Ok
   vector v ; // Ok
}

string ss ; // COMPILATION ERROR
vector vv ; // COMPILATION ERROR

E incluso puede limitar la "contaminación" a una clase:

#include <string>
#include <vector>

{
   using std::string ;
   string s ; // Ok
   vector v ; // COMPILATION ERROR
}

string ss ; // COMPILATION ERROR
vector vv ; // COMPILATION ERROR

Este "patrón" es obligatorio para el uso adecuado del lenguaje de intercambio casi estándar.

Y esto es imposible de hacer con métodos estáticos en las clases.

Por lo tanto, los espacios de nombres C ++ tienen su propia semántica.

Pero va más allá, ya que puede combinar espacios de nombres de forma similar a la herencia.

Por ejemplo, si tiene un espacio de nombres A con una función AAA, un espacio de nombres B con una función BBB, puede declarar un espacio de nombres C y traer AAA y BBB en este espacio de nombres con la palabra clave usando.

Conclusión

Los espacios de nombres son para espacios de nombres. Las clases son para clases.

C ++ fue diseñado para que cada concepto sea diferente y se use de manera diferente, en diferentes casos, como una solución a diferentes problemas.

No use clases cuando necesite espacios de nombres.

Y en su caso, necesita espacios de nombres.

paercebal
fuente
¿Se puede aplicar esta respuesta a los hilos también, es decir, es mejor usar espacios de nombres en lugar de métodos estáticos para los hilos?
guiones
3
@dashesy: ​​los espacios de nombres frente a los métodos estáticos no tienen nada que ver con hilos, por lo que sí, los espacios de nombres son mejores porque los espacios de nombres son casi siempre mejores que los métodos estáticos. En primer lugar, los métodos estáticos tienen acceso a las variables miembro de la clase, por lo que de alguna manera tienen un valor de encapsulación más bajo que los espacios de nombres. Y aislar datos es aún más importante en la ejecución de subprocesos.
paercebal
@ paercebal- gracias, estaba usando los métodos de clase estática para funciones de hilo. Ahora entiendo que estaba usando mal la clase como espacio de nombres, entonces, ¿cuál crees que es el mejor enfoque para tener múltiples hilos en un objeto? He hecho esta pregunta en lo mismo, aprecio si puede arrojar algo de luz (aquí o en la propia pregunta)
dashesy
1
@dashesy: ​​Estás pidiendo problemas. Lo que desea con diferentes subprocesos es aislar los datos que no deben compartirse, por lo que tener múltiples subprocesos con acceso privilegiado a los datos privados de una clase es una mala idea. Ocultaría un hilo dentro de una clase y me aseguraría de aislar los datos de ese hilo de los datos del hilo principal. Por supuesto, los datos que se supone que deben compartirse pueden ser miembros de esa clase, pero aún deben estar sincronizados (bloqueos, atómicos, etc.). No estoy seguro de a cuántas bibliotecas tiene acceso, pero usar tareas / async es aún mejor.
paercebal
¡La respuesta de paercebal debe ser la aceptada! Solo un enlace más para el intercambio casi estándar () a través del espacio de nombres + ADL -> stackoverflow.com/questions/6380862/…
Gob00st
54

Hay muchas personas que no estarían de acuerdo conmigo, pero así es como lo veo:

Una clase es esencialmente una definición de cierto tipo de objeto. Los métodos estáticos deben definir operaciones que estén íntimamente ligadas a esa definición de objeto.

Si solo va a tener un grupo de funciones relacionadas no asociadas con un objeto subyacente o una definición de un tipo de objeto , entonces diría ir solo con un espacio de nombres. Solo para mí, conceptualmente, esto es mucho más sensato.

Por ejemplo, en su caso, pregúntese: "¿Qué es un MyMath?" Si MyMathno define un tipo de objeto, entonces yo diría: no hacen que sea una clase.

Pero como dije, sé que hay mucha gente que (incluso con vehemencia) no está de acuerdo conmigo en esto (en particular, los desarrolladores de Java y C #).

Dan Tao
fuente
3
Tienes una perspectiva muy pura sobre esto. Pero en la práctica, una clase con todos los métodos estáticos puede ser útil: puede typedef, usarlos como parámetros de plantilla, etc.
Shog9
56
Eso es porque Jave y C # las personas no tienen otra opción.
Martin York
77
@ shog9. ¡También puedes modelar funciones!
Martin York
66
@Dan: presumiblemente, uno que necesitaba rutinas matemáticas y quería admitir "enchufar" diferentes implementaciones.
Shog9
1
@Dan: "Creo que si alguien está interesado en usar una clase como parámetro de plantilla, esa clase seguramente definirá algún objeto subyacente". No, en absoluto. Piensa en los rasgos. (Sin embargo, estoy totalmente de acuerdo con su respuesta.)
sbi
18
  • Si necesita datos estáticos, use métodos estáticos.
  • Si son funciones de plantilla y desea poder especificar un conjunto de parámetros de plantilla para todas las funciones juntas, utilice métodos estáticos en una clase de plantilla.

De lo contrario, use funciones de espacio de nombres.


En respuesta a los comentarios: sí, los métodos estáticos y los datos estáticos tienden a ser utilizados en exceso. Es por eso que ofrecí solo dos escenarios relacionados donde creo que pueden ser útiles. En el ejemplo específico del OP (un conjunto de rutinas matemáticas), si quisiera la capacidad de especificar parámetros, por ejemplo, un tipo de datos básicos y precisión de salida, que se aplicarían a todas las rutinas, podría hacer algo como:

template<typename T, int decimalPlaces>
class MyMath
{
   // routines operate on datatype T, preserving at least decimalPlaces precision
};

// math routines for manufacturing calculations
typedef MyMath<double, 4> CAMMath;
// math routines for on-screen displays
typedef MyMath<float, 2> PreviewMath;

Si no es necesario que, a continuación, por todos los medios utilizar un espacio de nombres.

Shog9
fuente
2
Los llamados datos estáticos pueden ser datos de nivel de espacio de nombres en el archivo de implementación del espacio de nombres, esto reduce aún más el acoplamiento ya que no tiene que aparecer en el encabezado.
Motti
Los datos estáticos no son mejores que los globales de espacio de nombres.
coppro
@coppro. Son al menos un paso más allá de la cadena evolutiva de los globales al azar, ya que pueden hacerse privados (pero de lo contrario están de acuerdo).
Martin York
@Motti: OTOH, si lo quieres en el encabezado (funciones en línea / plantilla), volverás a ser feo al respecto.
Shog9
1
Ejemplo interesante, ¡usar una clase como una forma abreviada para evitar repetir templateargumentos!
underscore_d
13

Debe usar un espacio de nombres, porque un espacio de nombres tiene muchas ventajas sobre una clase:

  • No tiene que definir todo en el mismo encabezado
  • No necesita exponer toda su implementación en el encabezado
  • No puedes usingun miembro de la clase; puedes usingun miembro del espacio de nombres
  • No puedes using class, aunque using namespaceno siempre es una buena idea
  • El uso de una clase implica que hay un objeto para crear cuando realmente no hay ninguno

Los miembros estáticos son, en mi opinión, muy, muy sobreutilizados. No son una necesidad real en la mayoría de los casos. Las funciones de miembros estáticos probablemente estén mejor como funciones de alcance de archivo, y los miembros de datos estáticos son solo objetos globales con una mejor reputación inmerecida.

coppro
fuente
3
"No necesita exponer toda su implementación en el encabezado" ni tampoco cuando usa una clase.
Vanuan
Aún más: si está utilizando espacios de nombres, no puede exponer toda su implementación en el encabezado (terminará con una definición múltiple de símbolos). Las funciones de miembros de clase en línea le permiten hacer eso.
Vanuan
1
@Vanuan: puede exponer implementaciones de espacio de nombres en el encabezado. Simplemente use la inlinepalabra clave para satisfacer ODR.
Thomas Eding
@ThomasEding no necesita! =
Can
3
@Vanuan: el compilador garantiza una sola cosa cuando se usa inline, y NO es "alinear" el cuerpo de una función. El propósito real (y garantizado por el estándar) inlinees evitar múltiples definiciones. Lea sobre "Una regla de definición" para C ++. Además, la pregunta SO vinculada no se estaba compilando debido a problemas de encabezado precompilados en lugar de problemas de ODR.
Thomas Eding
3

Preferiría espacios de nombres, de esa manera puede tener datos privados en un espacio de nombres anónimo en el archivo de implementación (para que no tenga que aparecer en el encabezado en lugar de los privatemiembros). Otro beneficio es que, por usingsu espacio de nombres, los clientes de los métodos pueden optar por no especificarMyMath::

Motti
fuente
2
También puede tener datos privados en un espacio de nombres anónimo en el archivo de implementación con clases. No estoy seguro de seguir tu lógica.
Patrick Johnmeyer
0

Una razón más para usar la clase: opción para hacer uso de los especificadores de acceso. Luego, posiblemente pueda dividir su método estático público en métodos privados más pequeños. El método público puede llamar a varios métodos privados.

Milind T
fuente
66
Los modificadores de acceso son geniales, pero incluso el privatemétodo más accesible es más accesible que un método cuyo prototipo no se publica en absoluto en un encabezado (y, por lo tanto, permanece invisible). Ni siquiera estoy mencionando la mejor encapsulación que ofrecen las funciones de espacios de nombres anónimos.
paercebal
2
Los métodos privados son, en mi opinión, inferiores a ocultar la función en sí misma en la implementación (archivo cpp) y nunca exponerla en el archivo de encabezado. Explique esto en su respuesta y por qué preferiría usar miembros privados . Hasta entonces -1.
nonsensickle
@nonsensickle Quizás quiera decir que una función gigantesca con muchas secciones repetidas se puede desglosar de forma segura mientras se ocultan las subsecciones ofensivas detrás de las privadas, evitando que otros las alcancen si son peligrosas / necesitan un uso muy cuidadoso.
Troyseph
1
@Troyseph, aun así, puede ocultar esta información dentro de un espacio de nombres sin nombre en el .cpparchivo, lo que lo haría privado a esa unidad de traducción sin dar ninguna información superflua a nadie que lea el archivo de encabezado. Efectivamente, estoy tratando de defender el idioma de PIMPL.
nonsensickle
No puede ponerlo en un .cpparchivo si desea usar plantillas.
Yay295
0

Tanto el espacio de nombres como el método de clase tienen sus usos. El espacio de nombres tiene la capacidad de extenderse a través de los archivos, sin embargo, eso es una debilidad si necesita aplicar todo el código relacionado para ir en un archivo. Como se mencionó anteriormente, la clase también le permite crear miembros estáticos privados en la clase. Puede tenerlo en el espacio de nombres anónimo del archivo de implementación, sin embargo, sigue siendo un alcance mayor que tenerlos dentro de la clase.

rocd
fuente
"[almacenar cosas] en el espacio de nombres anónimo del archivo de implementación [es] un alcance mayor que tenerlos dentro de la clase" - no, no lo es. En los casos en que no se requiere acceso privilegiado a los miembros, las cosas con espacios de nombres anónimos son más privadas que las private:propias. y en muchos casos donde parece que se requiere acceso privilegiado , eso se puede factorizar. la función más 'privada' es aquella que no aparece en un encabezado. private:Los métodos nunca pueden disfrutar de este beneficio.
underscore_d