¿Dónde debo poner funciones que no están relacionadas con una clase?

47

Estoy trabajando en un proyecto de C ++ donde tengo un montón de funciones matemáticas que inicialmente escribí para usar como parte de una clase. Sin embargo, a medida que escribía más código, me di cuenta de que necesitaba estas funciones matemáticas en todas partes.

¿Dónde está el mejor lugar para ponerlos? Digamos que tengo esto:

class A{
    public:
        int math_function1(int);
        ...
}

Y cuando escribo otra clase, no puedo (o al menos no sé cómo) usar eso math_function1en esa otra clase. Además, me he dado cuenta de que algunas de estas funciones no están realmente relacionadas con la clase A. Parecían estar al principio, pero ahora puedo ver cómo son solo funciones matemáticas.

¿Qué es una buena práctica en esta situación? En este momento los he copiado y pegado en las nuevas clases, lo que estoy seguro es la peor práctica.

Coco
fuente
11
¿Has aprendido sobre la staticpalabra clave?
S.Lott
31
En C ++, las funciones libres casi siempre se prefieren a las funciones miembro.
Pubby
44
No hay una regla que diga que todo tiene que estar en una clase. Al menos no en C ++.
tdammers
2
Preferiría un espacio de nombres a una clase con un montón de métodos estáticos
Nick Keighley,

Respuestas:

71

C ++ puede tener funciones que no sean de método bien, si no pertenecen a una clase, no las coloque en una clase, solo colóquelas en el ámbito global u otro espacio de nombres

namespace special_math_functions //optional
{
    int math_function1(int arg)
    {
         //definition 
    }
}
jk.
fuente
66
+1 Esta es la solución más sensata, aunque el espacio de nombres adicional no parece necesario.
Pubby
1
no, no es necesario
jk.
27
Algún espacio de nombres es útil para reducir posibles colisiones de nombres con otras bibliotecas.
Bill Door
11
Usar un espacio de nombres también es bueno porque desambigua si la llamada es un método o una función. ( math_function1(42)podría llamar a un miembro de la clase actual; special_math_functions::math_function1(42)claramente llama a una función independiente). Dicho esto, ::math_function(42)proporciona la misma desambiguación.
ipeet
2
Los espacios de nombres no son necesarios, pero tampoco están prohibidos. Por eso dice esta respuesta // optional. Sazone según el gusto.
user253751
6

Depende de cómo esté organizado el proyecto y qué tipo de patrones de diseño está utilizando, suponiendo que este sea estrictamente un código de utilidad, tiene las siguientes opciones:

  • Si no tiene que usar objetos para todo, puede hacer algo simple como ponerlos todos en un archivo sin un envoltorio de clase a su alrededor. Esto podría ser con o sin un espacio de nombres, aunque se recomienda el espacio de nombres para evitar problemas en el futuro.
  • Para C ++ administrado, puede crear una clase estática para contenerlos a todos; sin embargo, esto realmente no funciona igual que una clase real y entiendo que es un antipatrón de C ++.
  • Si no está utilizando C ++ administrado, puede usar funciones estáticas para permitirle acceder a ellas y tenerlas todas contenidas en una sola clase. Esto puede ser útil si también hay otras funciones para las que desearía que un objeto instanciado adecuado también sea un antipatrón.
  • Si desea asegurarse de que solo exista una instancia del objeto que contiene las funciones, puede usar el Patrón Singleton para una clase de utilidad que también le permite cierta flexibilidad en el futuro, ya que ahora tiene acceso a atributos no estáticos. Esto será de uso limitado y solo se aplica realmente si necesita un objeto por alguna razón. Lo más probable es que si haces esto ya sabrás por qué.

Tenga en cuenta que la primera opción será la mejor apuesta y las tres siguientes son de utilidad limitada. Sin embargo, dicho esto, es posible que se encuentre solo debido a que los programadores de C # o Java realizan algún trabajo en C ++ o si alguna vez trabaja en código C # o Java donde el uso de clases es obligatorio.

rjzii
fuente
¿Por qué el voto negativo?
rjzii
10
No soy el votante, pero probablemente porque estás recomendando una clase con funciones estáticas o un singleton, mientras que las funciones libres probablemente estarían bien en este caso (y son aceptables y útiles para muchas cosas en C ++).
Anton Golov
@AntonGolov: las funciones gratuitas son lo primero que mencioné en la lista. :) El resto de ellos son los enfoques más orientados a OOP para situaciones en las que se trata "¡Todo debe ser una clase!" ambientes.
rjzii
99
@Rob Z: Sin embargo, C ++ no es uno de esos "¡Todo debe ser una clase!" ambientes.
David Thornley
1
¿Desde cuándo es POO forzar funciones puras en una clase? Parece más como OOP-cargo-cult.
Deduplicador
1

Como ya dijo, copiar y pegar el código es la peor forma de reutilización de código. Si tiene funciones que no pertenecen a ninguna de sus clases o que se pueden usar para varios escenarios, el mejor lugar para ubicarlas sería una clase auxiliar o de utilidad. Si no usan ningún dato de instancia, pueden volverse estáticos, por lo que no necesita crear una instancia de la clase de utilidad para usarlo.

Vea aquí una discusión sobre las funciones miembro estáticas en C ++ nativo y aquí para clases estáticas en C ++ administrado. Entonces podría usar esta clase de utilidad donde hubiera pegado su código.

En .NET, por ejemplo, cosas como Min()y Max()se proporcionan como miembros estáticos en la System.Mathclase .

Si todas sus funciones están relacionadas con las matemáticas y, de lo contrario, tendría una Mathclase gigantesca , es posible que desee desglosarla más y tener clases como TrigonometryUtilities, EucledianGeometryUtilitiesy así sucesivamente.

Otra opción sería poner la funcionalidad compartida en una clase base de las clases que requieren dicha funcionalidad. Esto funciona bien, cuando las funciones en las preguntas deben operar en datos de instancia, sin embargo, este enfoque también es menos flexible si desea evitar la herencia múltiple y solo se adhiere a una clase base, porque estaría "usando" su base única clase solo para obtener acceso a alguna funcionalidad compartida.

PersonalNexus
fuente
18
En mi humilde opinión, las clases de utilidad con nada más que miembros estáticos son un antipatrón en C ++. Estás utilizando una clase para reproducir perfectamente el comportamiento de un espacio de nombres, lo que realmente no tiene sentido.
ipeet
+1 por mencionar clases de utilidad. Los lenguajes como C # requieren que todo esté en una clase, por lo que es bastante común crear una serie de clases de utilidad para diversos fines. La implementación de estas clases como estática hace que las utilidades sean aún más fáciles de usar y evita los dolores de cabeza que la herencia a veces puede crear, particularmente cuando las clases base se están hinchando con código que solo puede ser utilizado por uno o dos descendientes. Se pueden aplicar técnicas similares en otros idiomas para proporcionar un contexto significativo para sus funciones de utilidad, en lugar de dejarlas flotando en el ámbito global.
S.Robins
55
@ S.Robins: no hay necesidad de algo así en C ++, solo puede ponerlos en un espacio de nombres, que tiene exactamente el mismo efecto.
DeadMG
0

Desambigua el término "función auxiliar". Una definición es una función de conveniencia que usa todo el tiempo solo para hacer un trabajo. Esos pueden vivir en el espacio de nombres principal y tener sus propios encabezados, etc. La otra definición de función auxiliar es una función de utilidad para una sola clase o familia de clases.

// a general helper 
template <class T>
bool isPrinter(T& p){
   return (dynamic_cast<Printer>(p))? true: false;
}

    // specific helper for printers
namespace printer_utils {    
  namespace HP {
     print_alignment_page() { printAlignPage();}
  }

  namespace Xerox {
     print_alignment_page() { Alignment_Page_Print();}
  }

  namespace Canon {
     print_alignment_page() { AlignPage();}
  }

   namespace Kyocera {
     print_alignment_page() { Align(137,4);}
   }

   namespace Panasonic {
      print_alignment_page() { exec(0xFF03); }
   }
} //namespace

Ahora isPrinterestá disponible para cualquier código, incluido su encabezado, pero print_alignment_pagerequiere una using namespace printer_utils::Xerox;directiva. También se puede referirlo como

Canon::print_alignment_page();

para ser más claro

El STL de C ++ tiene el std::espacio de nombres que cubre casi todas sus clases y funciones, pero las divide categóricamente en más de 17 encabezados diferentes para permitir que el codificador elimine los nombres de clase, nombres de funciones, etc. si quieren escribir los suyos

De hecho, NO se recomienda usar using namespace std;en un archivo de encabezado o, como se hace a menudo, como la primera línea dentro main(). std::es de 5 letras y, a menudo, parece una tarea rutinaria para introducir la función que uno quiere usar (¡especialmente std::couty std::endl!) pero tiene un propósito.

El nuevo C ++ 11 tiene algunos espacios de nombres secundarios para servicios especiales como

std::placeholders,
std::string_literals,
std::chrono,
std::this_thread,
std::regex_constants

que se puede traer para su uso.

Una técnica útil es la composición del espacio de nombres . Uno define un espacio de nombres personalizado para contener los espacios de nombres que necesita para su .cpparchivo en particular y usarlo en lugar de un conjunto de usingdeclaraciones para cada cosa en un espacio de nombres que pueda necesitar.

#include <iostream>
#include <string>
#include <vector>

namespace Needed {
  using std::vector;
  using std::string;
  using std::cout;
  using std::endl;
}

int main(int argc, char* argv[])
{
  /*  using namespace std; */
      // would avoid all these individual using clauses,
      // but this way only these are included in the global
      // namespace.

 using namespace Needed;  // pulls in the composition

 vector<string> str_vec;

 string s("Now I have the namespace(s) I need,");

 string t("But not the ones I don't.");

 str_vec.push_back(s);
 str_vec.push_back(t);

 cout << s << "\n" << t << endl;
 // ...

Esta técnica limita la exposición al conjunto std:: namespacees grande! ) Y permite escribir código más limpio para las líneas de código más comunes que las personas escriben con más frecuencia.

Chris Reid
fuente
-2

Es posible que desee ponerlo en una función de plantilla para que esté disponible para diferentes tipos de enteros y / o flotantes:

template <typename T>
T math_function1(T){
 ..
}

También podría crear tipos personalizados limpios que representen, por ejemplo, números enormes o números complejos al sobrecargar operadores relevantes para su tipo personalizado para que sean amigables con las plantillas.

usuario1703394
fuente