web-dev-qa-db-fra.com

Où dois-je placer les fonctions qui ne sont pas liées à une classe?

Je travaille sur un projet C++ où j'ai un tas de fonctions mathématiques que j'ai initialement écrit pour utiliser dans le cadre d'une classe. Cependant, en écrivant plus de code, j'ai réalisé que j'avais besoin de ces fonctions mathématiques partout.

Où est le meilleur endroit pour les mettre? Disons que j'ai ceci:

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

Et quand j'écris une autre classe, je ne peux pas (ou du moins je ne sais pas comment) utiliser ça math_function1 dans cette autre classe. De plus, j'ai réalisé que certaines de ces fonctions ne sont pas vraiment liées à la classe A. Elles semblaient l'être au début, mais maintenant je peux voir comment ce ne sont que des fonctions mathématiques.

Quelle est la bonne pratique dans cette situation? En ce moment, je les ai copiés-collés dans les nouvelles classes, ce qui, j'en suis sûr, est la pire pratique.

51
coconut

C++ peut avoir des fonctions non-méthodes très bien, si elles n'appartiennent pas à une classe, ne les mettez pas dans une classe, mettez-les simplement à la portée globale ou à un autre espace de noms

namespace special_math_functions //optional
{
    int math_function1(int arg)
    {
         //definition 
    }
}
74
jk.

Dépend de la façon dont le projet est organisé et du type de modèles de conception que vous utilisez, en supposant qu'il s'agit uniquement de code utilitaire, vous disposez des options suivantes:

  • Si vous n'êtes pas obligé d'utiliser des objets pour tout, vous pouvez faire quelque chose de simple comme simplement les mettre tous dans un fichier sans un wrapper de classe autour d'eux. Cela peut être avec ou sans espace de noms, bien que l'espace de noms soit recommandé pour éviter tout problème à l'avenir.
  • Pour le C++ managé, vous pouvez créer un classe statique pour les contenir tous; cependant, cela ne fonctionne pas vraiment de la même manière qu'une classe réelle et je crois comprendre qu'il s'agit d'un anti-modèle C++.
  • Si vous n'utilisez pas le C++ managé, vous pouvez simplement utiliser fonctions statiques pour vous permettre d'y accéder et de les avoir tous contenus dans une seule classe. Cela peut être utile s'il existe également d'autres fonctions pour lesquelles vous souhaitez qu'un objet instancié approprié soit également un anti-modèle.
  • Si vous voulez vous assurer qu'une seule instance de l'objet contenant les fonctions existera, vous pouvez utiliser le Singleton Pattern pour une classe utilitaire qui vous permet également une certaine flexibilité à l'avenir car vous avez maintenant accès à attributs non statiques. Cela va être d'une utilité limitée et ne s'applique vraiment que si vous avez besoin d'un objet pour une raison quelconque. Les chances sont que si vous faites cela, vous saurez déjà pourquoi.

Notez que la première option va être le meilleur pari et les trois suivantes sont d'une utilité limitée. Cela dit cependant, vous pourriez rencontrer alors juste à cause de C # ou Java font du travail C++ ou si vous travaillez sur C # ou Java code où l'utilisation est des cours est obligatoire.

7
rjzii

Comme vous l'avez déjà dit, le copier-coller du code est la pire forme de réutilisation de code. Si vous avez des fonctions qui n'appartiennent à aucune de vos classes ou qui peuvent être utilisées dans plusieurs scénarios, le meilleur endroit pour les mettre serait une classe auxiliaire ou utilitaire. S'ils n'utilisent aucune donnée d'instance, ils peuvent être rendus statiques, vous n'avez donc pas besoin de créer une instance de la classe utilitaire pour l'utiliser.

Voir ici pour une discussion sur les fonctions membres statiques en C++ natif et ici pour les classes statiques en C++ managé. Vous pouvez ensuite utiliser cette classe utilitaire partout où vous auriez collé votre code.

Dans .NET par exemple, des choses comme Min() et Max() sont fournies en tant que membres statiques sur System.Math classe .

Si toutes vos fonctions sont liées aux mathématiques et que vous auriez une classe Math gigantesque, vous voudrez peut-être la décomposer davantage et avoir des classes comme TrigonometryUtilities, EucledianGeometryUtilities et ainsi sur.

Une autre option serait de mettre une fonctionnalité partagée dans une classe de base des classes nécessitant ladite fonctionnalité. Cela fonctionne bien, lorsque les fonctions des questions doivent fonctionner sur des données d'instance, cependant, cette approche est également moins flexible si vous voulez éviter l'héritage multiple et ne vous en tenir qu'à une seule classe de base, car vous "utiliseriez" votre base unique classe juste pour avoir accès à certaines fonctionnalités partagées.

1
PersonalNexus

Supprimez l'ambiguïté du terme "fonction d'assistance". Une définition est une fonction pratique que vous utilisez tout le temps juste pour faire un travail. Ceux-ci peuvent vivre dans l'espace de noms principal et avoir leurs propres en-têtes, etc. L'autre définition de fonction d'assistance est une fonction utilitaire pour une seule classe ou famille de classes.

// 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

isPrinter est désormais disponible pour tout code, y compris son en-tête, mais print_alignment_page Nécessite une directive using namespace printer_utils::Xerox;. On peut aussi le référencer comme

Canon::print_alignment_page();

pour être plus clair.

Le C++ STL a l'espace de noms std:: Qui couvre presque toutes ses classes et fonctions, mais il les décompose catégoriquement en plus de 17 en-têtes différents pour permettre au codeur d'obtenir les noms de classe, les noms de fonction, etc. façon s'ils veulent écrire leur propre.

En fait, il n'est PAS recommandé d'utiliser using namespace std; Dans un fichier d'en-tête ou, comme c'est souvent le cas, comme première ligne à l'intérieur de main(). std:: Est composé de 5 lettres et semble souvent une corvée pour préfacer la fonction que l'on veut utiliser (en particulier std::cout Et std::endl!)! Mais cela sert un but.

Le nouveau C++ 11 contient des sous-espaces de noms pour des services spéciaux tels que

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

qui peut être amené pour utilisation.

Une technique utile est composition d'espace de noms. L'un définit un espace de noms personnalisé pour contenir les espaces de noms dont vous avez besoin pour votre fichier .cpp Particulier et l'utiliser à la place d'un tas d'instructions using pour chaque chose dans un espace de noms dont vous pourriez avoir besoin.

#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;
 // ...

Cette technique limite l'exposition à l'ensemble std:: namespace (c'est grand!) et permet d'écrire du code plus propre pour les lignes de code les plus courantes que les gens écrivent le plus souvent.

0
Chris Reid