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

47

Je travaille sur un projet C ++ dans lequel j'ai un tas de fonctions mathématiques que j'ai initialement écrites pour les utiliser dans le cadre d'un cours. En écrivant plus de code, cependant, 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 dans une autre classe, je ne peux pas (ou au moins je ne sais pas comment) l'utiliser math_function1dans 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 en être au début, mais je peux maintenant voir à quel point elles ne sont que des fonctions mathématiques.

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

noix de coco
la source
11
Avez-vous appris sur le staticmot-clé?
S.Lott
31
En C ++, les fonctions libres sont presque toujours préférées aux fonctions membres.
Pubby
4
Aucune règle ne dit que tout doit être dans une classe. Du moins pas en C ++.
tdammers
2
Je préférerais un espace de noms à une classe avec un tas de méthodes statiques
Nick Keighley

Réponses:

71

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, mais placez-les simplement dans une portée globale ou dans un autre espace de noms

namespace special_math_functions //optional
{
    int math_function1(int arg)
    {
         //definition 
    }
}
jk.
la source
6
+1 C'est la solution la plus judicieuse, bien que l'espace de noms supplémentaire ne semble pas nécessaire.
Pubby
1
non ce n'est pas nécessaire
jk.
27
Certains espaces de noms sont utiles pour réduire les conflits de noms potentiels avec d'autres bibliothèques.
Bill Door
11
L'utilisation d'un espace de noms est également intéressante car elle permet de déterminer si l'appel est une méthode ou une fonction. ( math_function1(42)peut appeler un membre de la classe actuelle; special_math_functions::math_function1(42)appelle clairement une fonction indépendante). Cela étant dit, ::math_function(42)fournit la même homonymie.
ipeet
2
Les espaces de noms ne sont pas nécessaires mais ils ne sont pas non plus interdits. D'où pourquoi cette réponse dit // optional. Assaisonner selon les goûts.
user253751
6

Cela dépend de la façon dont le projet est organisé et du type de modèle de conception que vous utilisez. En supposant qu'il s'agisse strictement d'un 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 de simplement les placer tous dans un fichier sans encapsuleur. Cela peut être avec ou sans un espace de noms, bien qu'il soit recommandé d'éviter tout problème à l'avenir.
  • Pour le C ++ géré, vous pouvez créer une classe statique pour les contenir tous. Cependant, cela ne fonctionne pas vraiment comme une classe réelle et ma compréhension est qu'il s'agit d'un anti-motif C ++.
  • Si vous n'utilisez pas le C ++ géré, vous pouvez simplement utiliser des fonctions statiques pour vous permettre d'y accéder et de toutes les contenir dans une même classe. Cela peut être utile s'il existe également d'autres fonctions pour lesquelles vous souhaitez un objet instancié correct, car cela peut également être un anti-motif.
  • Si vous voulez vous assurer qu'il n'y aura qu'une seule instance de l'objet contenant les fonctions, vous pouvez utiliser le modèle Singleton pour une classe d'utilitaires, ce qui vous permet également une certaine flexibilité à l'avenir, car vous avez désormais accès à des 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 sera la meilleure option et que les trois suivantes sont d'une utilité limitée. Cela dit, vous risquez peut-être de rencontrer des programmeurs C # ou Java effectuant certains travaux C ++ ou de travailler sur du code C # ou Java dans lequel l'utilisation de classes est obligatoire.

rjzii
la source
Pourquoi le vote à la baisse?
Rjzii
10
Je ne suis pas le votant inférieur, mais probablement parce que vous conseillez une classe avec des fonctions statiques ou un singleton, alors que les fonctions libres seraient probablement bien dans ce cas (et sont acceptables et utiles pour beaucoup de choses en C ++).
Anton Golov
@AntonGolov - Les fonctions libres sont la première chose que j'ai mentionnée dans la liste. :) Le reste d'entre eux sont les approches plus orientées POO pour les situations où vous avez affaire à "Tout doit être une classe!" environnements.
Rjzii
9
@Rob Z: Cependant, le C ++ n'est pas un de ceux-là "Tout doit être une classe!" environnements.
David Thornley
1
Depuis quand est-il OOP de forcer des fonctions pures dans une classe? On dirait plus du culte de la POO du fret.
Déduplicateur
1

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

Voir ici pour une discussion des fonctions membres statiques en C ++ natif et ici pour les classes statiques en C ++ géré. Vous pouvez ensuite utiliser cette classe d’utilitaires partout où vous auriez collé votre code.

Dans .NET par exemple, des éléments tels que Min()et Max()sont fournis en tant que membres statiques de la System.Mathclasse .

Si toutes vos fonctions sont liées aux mathématiques et que vous auriez autrement une Mathclasse gigantesque , vous voudrez peut-être la décomposer plus loin et avoir des classes comme TrigonometryUtilities, EucledianGeometryUtilitieset ainsi de suite.

Une autre option serait de placer une fonctionnalité partagée dans une classe de base des classes nécessitant ladite fonctionnalité. Cela fonctionne bien lorsque les fonctions en question doivent opérer sur des données d'instance. Cependant, cette approche est également moins flexible si vous souhaitez éviter les héritages multiples 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.

PersonalNexus
la source
18
IMHO, les classes utilitaires n'ayant que des membres statiques sont un anti-modèle en C ++. Vous utilisez une classe pour reproduire parfaitement le comportement d'un espace de noms, ce qui n'a aucun sens.
ipeet
+1 pour mentionner les classes utilitaires. Les langages tels que C # exigent que tout soit dans une classe, il est donc assez courant de créer un certain nombre de classes d’utilitaires à des fins diverses. L'implémentation de ces classes en tant que Static rend les utilitaires encore plus conviviaux et évite les ennuis que peut parfois créer l'héritage, en particulier lorsque les classes de base deviennent saturées de code qui ne peut être utilisé que par un ou deux descendants. Des techniques similaires peuvent être appliquées dans d'autres langages pour fournir un contexte significatif à vos fonctions utilitaires, plutôt que de les laisser flotter dans la portée globale.
S.Robins
5
@ S.Robins: Rien de tel en C ++, vous pouvez simplement les mettre dans un espace de noms, qui a exactement le même effet.
DeadMG
0

Désambiguïsez le terme "fonction d'assistance". Une définition est une fonction pratique que vous utilisez tout le temps pour faire votre travail. Ceux-ci peuvent vivre dans l'espace de noms principal et avoir leurs propres en-têtes, etc. L'autre définition de la fonction d'assistance est une fonction utilitaire pour une classe ou une 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

Now isPrinterest disponible pour tout code incluant son en-tête, mais print_alignment_pagenécessite une using namespace printer_utils::Xerox;directive. On peut aussi le référencer comme

Canon::print_alignment_page();

être plus clair.

La STL C ++ a un std::espace de noms qui couvre la quasi-totalité de ses classes et fonctions, mais il les divise catégoriquement en plus de 17 en-têtes différents pour permettre au codeur d'obtenir le nom de classe, le nom de fonction, etc. s'il souhaite écrire. les leurs.

En fait, il n'est PAS recommandé d'utiliser using namespace std;dans un fichier d'en-tête ou, comme on le fait souvent, comme première ligne à l'intérieur main(). std::est de 5 lettres et semble souvent une corvée à précéder de la fonction que l'on veut utiliser (surtout std::coutet std::endl!), mais cela sert à quelque chose.

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é à être utilisé.

Une technique utile est la composition d'espace de noms . On définit un espace de noms personnalisé pour contenir les espaces de noms dont vous avez besoin pour votre .cppfichier particulier et les utiliser à la place d'un ensemble d' usinginstructions pour chaque élément d'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 un code plus propre pour les lignes de code les plus courantes écrites par les utilisateurs.

Chris Reid
la source
-2

Vous voudrez peut-être l'insérer dans une fonction de modèle afin de le rendre disponible pour différents types d'entiers et / ou de flottants:

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

Vous pouvez également créer des types personnalisés ordonnés qui représentent, par exemple, des nombres énormes ou des nombres complexes en surchargeant les opérateurs pertinents pour votre type personnalisé afin de les rendre compatibles avec les modèles.

utilisateur1703394
la source