¿Son las clases de utilidad con nada más que miembros estáticos un antipatrón en C ++?

39

La pregunta ¿Dónde debería poner funciones que no están relacionadas con una clase ha provocado cierto debate sobre si tiene sentido en C ++ combinar funciones de utilidad en una clase o simplemente hacer que existan como funciones libres en un espacio de nombres?

Vengo de un fondo de C # donde la última opción no existe y, por lo tanto, naturalmente tiendo a usar clases estáticas en el pequeño código de C ++ que escribo. La respuesta más votada sobre esa pregunta, así como varios comentarios, dicen que las funciones libres son preferibles, incluso sugiriendo que las clases estáticas eran un antipatrón. ¿Por qué es así en C ++? Al menos en la superficie, los métodos estáticos en una clase parecen indistinguibles de las funciones libres en un espacio de nombres. ¿Por qué así la preferencia por este último?

¿Serían diferentes las cosas si la colección de funciones de utilidad necesitara datos compartidos, por ejemplo, un caché que uno pudiera almacenar en un campo estático privado?

PersonalNexus
fuente
Suena un poco como el antipatrón de "descomposición funcional".
user281377
77
Respuesta corta: simplemente no necesita una clase para ajustar estas funciones. Las funciones gratuitas son mucho más adecuadas para su tarea que agruparlas en una construcción pseudo-OO, que es una solución alternativa que solo necesita en lenguajes "puramente OO".
Chris dice que reinstala a Mónica

Respuestas:

39

Supongo que para responder que debemos comparar las intenciones de las clases y los espacios de nombres. De acuerdo con Wikipedia:

Clase

En la programación orientada a objetos, una clase es una construcción que se utiliza como un modelo para crear instancias de sí misma, denominadas instancias de clase, objetos de clase, objetos de instancia o simplemente objetos. Una clase define miembros constituyentes que permiten que estas instancias de clase tengan estado y comportamiento. Los miembros del campo de datos (variables miembro o variables de instancia) permiten que un objeto de clase mantenga el estado. Otros tipos de miembros, especialmente los métodos, permiten el comportamiento de un objeto de clase. Las instancias de clase son del tipo de la clase asociada.

Espacio de nombres

En general, un espacio de nombres es un contenedor que proporciona contexto para los identificadores (nombres, términos técnicos o palabras) que contiene, y permite la desambiguación de los identificadores de homónimos que residen en diferentes espacios de nombres.

Ahora, ¿qué estás tratando de lograr al poner las funciones en una clase (estáticamente) o un espacio de nombres? Apostaría a que la definición de un espacio de nombres describe mejor su intención: todo lo que desea es un contenedor para sus funciones. No necesita ninguna de las características descritas en la definición de clase. Tenga en cuenta que las primeras palabras de la definición de clase son " En la programación orientada a objetos ", sin embargo, no hay nada orientado a objetos en una colección de funciones.

Probablemente también hay razones técnicas, pero como alguien que viene de Java y trata de entender el lenguaje de múltiples paradigmas que es C ++, la respuesta más obvia para mí es: Porque no necesitamos OO para lograr esto.

Gyan alias Gary Buyn
fuente
1
+1 para "no necesitamos OO para lograr esto"
Ixrec
Estoy de acuerdo con esto como un no fanático de OO, pero supongo que hay algunas situaciones en las que usar una clase All-static es la única solución, por ejemplo, reemplazar un espacio de nombres, ya que no puede declarar un espacio de nombres anidado dentro una clase.
Wael Boutglay
26

Sería muy cauteloso al llamar a eso un antipatrón. Por lo general, se prefieren los espacios de nombres, pero como no hay plantillas de espacios de nombres y los espacios de nombres no se pueden pasar como parámetros de plantilla, el uso de clases con nada más que miembros estáticos es bastante común.

Un programador
fuente
3
Las otras respuestas han pasado por alto la última línea del OP. Los espacios de nombres son ámbitos con nombre, por lo que si necesita control de acceso, las clases son la única herramienta disponible.
cmannett85
2
@ cbamber85 para ser justos, había puesto esa línea solo después de leer las dos primeras respuestas.
PersonalNexus
@PersonalNexus ¡Ah, es justo, no me di cuenta!
cmannett85
10
@ cbamber85: Eso no es del todo cierto. Obtendrá una encapsulación aún mejor al poner funciones "privadas" en el archivo de implementación, de modo que los usuarios ni siquiera vean la declaración privada.
UncleBens
2
Las plantillas +1 son de hecho un punto donde los espacios de nombres aún carecen de características.
Chris dice reinstalar a Mónica
13

En el pasado necesitaba tomar una clase FORTRAN. Conocía otros idiomas imperativos para entonces, así que pensé que podía hacer FORTRAN sin estudiar mucho. Cuando entregué mi primera tarea, el profesor me la devolvió y me pidió que la volviera a hacer: dijo que los programas Pascal escritos en sintaxis FORTRAN no cuentan como presentaciones válidas.

Un problema similar está en juego aquí: el uso de clases estáticas para alojar funciones de utilidad en C ++ es un lenguaje extraño para C ++.

Como mencionó en su pregunta, el uso de clases estáticas para funciones de utilidad en C # es una necesidad: las funciones independientes simplemente no son una opción en C #. El lenguaje necesario para desarrollar un patrón que permita a los programadores definir funciones independientes de alguna otra manera, es decir, dentro de las clases de utilidad estática. Este fue un truco de Java tomado palabra por palabra: por ejemplo, java.lang.Math y System.Math de .NET son casi isomórficos.

C ++, sin embargo, ofrece espacios de nombres, una instalación diferente para lograr el mismo objetivo, y lo utiliza activamente en la implementación de su biblioteca estándar. Agregar una capa adicional de clases estáticas no solo es innecesario, sino también algo intuitivo para los lectores sin C # o fondo Java. En cierto sentido, está introduciendo una "traducción de préstamo" en el idioma para algo que se puede expresar de forma nativa.

Cuando sus funciones necesitan compartir datos, la situación es diferente. Debido a que sus funciones ya no están relacionadas , el patrón Singleton se convierte en la forma preferida de abordar este requisito.

dasblinkenlight
fuente
66
+1, excepto para recomendar singletons. Están no preferidos en C ++! Las funciones pueden estar en una clase si necesitan compartir el estado, pero las clases no deberían ser singletons a menos que realmente necesiten estarlo. Y generalmente no lo hacen.
Maxpm
@Maxpm No me malinterpreten, solo se prefiere singleton a las variables estáticas globales. Aparte de eso, no hay nada "preferido" al respecto.
dasblinkenlight
2
Existe este buen artículo, Singletons: Resolviendo problemas que no sabías que nunca tuviste desde 1995 . En resumen, dice que acoplar la instancia bien conocida a la clase en sí misma limita innecesariamente su flexibilidad y no le da nada. La mayoría de las veces solo tengo una clase y una instancia compartida en algún lugar, pero no la conviertas en singleton.
Jan Hudec
No estoy seguro de que "idioma extranjero" sea el término correcto. Es un idioma muy común. Creo que varios equipos de programación están utilizando e2 de Stroustrup ...
Nick Keighley
10

¿Quizás necesites preguntar por qué querrías una clase totalmente estática?

La única razón por la que puedo pensar es que otros lenguajes (Java y C #) que son mucho 'todo es una clase' los requieren. Estos lenguajes no pueden crear funciones de nivel superior, por lo que inventaron un truco para mantenerlos, y esa fue la función miembro estática. Son una solución alternativa, pero C ++ no necesita tal cosa, puede crear directamente nuevas funciones independientes de alto nivel.

Si necesita funciones que operan en un elemento de datos específico, entonces tiene sentido agruparlas en una clase que contenga los datos, pero luego, dejan de ser funciones y comienzan a ser miembros de esa clase que operan en los datos de la clase.

Si tiene una función que no opera en un tipo de datos en particular (uso la palabra aquí ya que las clases son formas de definir nuevos tipos de datos), entonces es realmente un antipatrón empujarlos a una clase, para nada más que fines semánticos

gbjbaanb
fuente
10
De hecho, diría que la "clase con todos los miembros estáticos" en Java es en realidad un truco para sortear una limitación de Java.
Charles Salvia
@CharlesSalvia: estaba siendo educado :) Tengo el mismo mal presentimiento acerca de que main () es parte de una clase de Java también, aunque entiendo por qué lo hicieron.
gbjbaanb
En realidad, esto parece proporcionar la respuesta de por qué querrías una clase completamente estática. ¿O te entiendo mal? Por ejemplo, necesito mantener una variable cuyo valor puede cambiar que es leída por una clase (que pretendo almacenar en ROM) y escrita por otra clase (que estará en la RAM). Simplemente colocarlo en una ubicación de ram conocida no es suficiente: envolverlo (y sus accesores) en una clase me permite ajustar el control de acceso. Más o menos lo que describe en su segundo párrafo.
iheanyi
5

Al menos en la superficie, los métodos estáticos en una clase parecen indistinguibles de las funciones libres en un espacio de nombres.

En otras palabras, tener una clase en lugar de un espacio de nombres no tiene ventajas.

¿Por qué así la preferencia por este último?

Por un lado, le ahorra escribir statictodo el tiempo, aunque podría decirse que es un beneficio menor.

El principal beneficio es que es la herramienta menos poderosa para el trabajo. Las clases se pueden usar para crear objetos, se pueden usar como el tipo de variables o como argumentos de plantilla. Ninguna de esas son características que desea para su colección de funciones. Por lo tanto, es preferible usar una herramienta que no tenga esas características, de modo que la clase no pueda ser mal utilizada accidentalmente.

A partir de eso, el uso de un espacio de nombres también deja en claro de inmediato a los usuarios de su código que se trata de una colección de funciones y no un plan para crear objetos.

sepp2k
fuente
5

... sin embargo, varios comentarios dicen que se prefieren las funciones libres, incluso sugiriendo que las clases estáticas eran un antipatrón. ¿Por qué es así en C ++? Al menos en la superficie, los métodos estáticos en una clase parecen indistinguibles de las funciones libres en un espacio de nombres. ¿Por qué así la preferencia por este último?

Una clase totalmente estática hará el trabajo, pero es como conducir un camión de 53 pies al supermercado para comprar papas fritas y salsa cuando lo hará un sedán de cuatro puertas (es decir, es excesivo). Las clases vienen con una pequeña cantidad de gastos adicionales y su existencia podría dar a alguien la impresión de que crear una instancia podría ser una buena idea. Las funciones gratuitas que ofrece C ++ (y C, donde todas las funciones son gratuitas) no lo hacen; son solo funciones y nada más.

¿Serían diferentes las cosas si la colección de funciones de utilidad necesitara datos compartidos, por ejemplo, un caché que uno pudiera almacenar en un campo estático privado?

Realmente no. El propósito original de staticen C (y luego C ++) era ofrecer almacenamiento persistente que se pueda utilizar en cualquier nivel de alcance:

int counter() {
    static int value = 0;
    value++;
}

Puede llevar el alcance al nivel de un archivo, lo que lo hace visible para todos los ámbitos más estrechos pero no fuera del archivo:

static int value = 0;

int up() { value++; }
int down() { value--; }

Un miembro privado de clase estática en C ++ tiene el mismo propósito pero está limitado al alcance de una clase. La técnica utilizada en el counter()ejemplo anterior también funciona dentro de los métodos de C ++ y es algo que realmente recomendaría hacer si la variable no necesita ser visible para toda la clase.

Blrfl
fuente
Supongo que un grupo de funciones de utilidad no relacionadas que no comparten datos debería agruparse mejor en un espacio de nombres. Pero si tiene un grupo de funciones de utilidad estrechamente acopladas que necesitan acceso a datos compartidos y quizás algún control de acceso, una clase estática es la mejor opción. Una estática dentro de una función no es reentrante. Usar un módulo global ... un poco mejor en este contexto, pero sigo pensando que una clase totalmente estática es la mejor solución si tiene los requisitos que describí.
iheanyi
Si comparten datos, ¿podrían ser mejores como clase sin métodos estáticos?
Nick Keighley
@NickKeighley Eso depende de quién gane el debate sobre si es mejor crear una instancia y pasarla a todo lo que la necesite o simplemente hacerla estática en su clase.
Blrfl
1

Si una función no mantiene un estado y es reentrante, no parece tener mucho sentido meterla dentro de una clase (a menos que el idioma lo obligue). Si la función mantiene algún estado (por ejemplo, solo puede hacerse segura para subprocesos mediante un mutex estático), entonces los métodos estáticos en una clase parecen apropiados.

Martin James
fuente
1

Gran parte del discurso sobre el tema aquí tiene sentido, aunque hay algo muy fundamental sobre C ++ que hace que namespaces y classen / structs muy diferente.

Las clases estáticas (clases donde todos los miembros son estáticos, y la clase nunca se instanciará) son en sí mismas objetos. No son simplemente unnamespace funciones para contener.

La metaprogramación de plantillas nos permite utilizar una clase estática como objeto de tiempo de compilación.

Considera esto:

template<typename allocator_type> class allocator
{
public:
    inline static void* allocate(size_t size)
    {
        return allocator_type::template allocate(size);
    }
    inline static void release(void* p)
    {
        allocator_type::template release(p);
    }
};

Para usarlo necesitamos funciones contenidas dentro de una clase. Un espacio de nombres no funcionará aquí. Considerar:

class mallocator
{
    inline static void* allocate(size_t size)
    {
        return std::malloc(size);
    }
    inline static void release(void* p)
    {
        return std::free(p);
    }
};

Ahora para usarlo:

using my_allocator = allocator<mallocator>;

void* p = my_allocator::allocate(1024);
...
my_allocator::release(p);

Siempre que un nuevo asignador exponga un allocaterelease función y que sea compatible, cambiar a un nuevo asignador es fácil.

Esto no se puede lograr con espacios de nombres.

¿Siempre necesitas funciones para ser parte de una clase? No.

¿Usar una clase estática es un antipatrón? Depende del contexto.

¿Serían diferentes las cosas si la colección de funciones de utilidad necesitara datos compartidos, por ejemplo, un caché que uno pudiera almacenar en un campo estático privado?

En ese caso, es probable que lo que intente lograr sea mejor a través de la programación orientada a objetos.

Michael Gazonda
fuente
El uso de la palabra clave en línea es redundante aquí ya que los métodos definidos en una definición de clase están implícitamente en línea. Consulte en.cppreference.com/w/cpp/language/inline .
Trevor
1

Como John Carmack dijo:

"A veces, la implementación elegante es solo una función. No es un método. No es una clase. No es un marco. Solo una función".

Creo que eso lo resume bastante bien. ¿Por qué lo convertirías con fuerza en una clase si claramente no es una clase? C ++ tiene el lujo de que realmente puedes usar funciones; En Java, todo es un método. Entonces necesitas clases de utilidad, y muchas de ellas.

juhist
fuente