Dove devo mettere le funzioni che non sono correlate a una classe?


47

Sto lavorando a un progetto C ++ in cui ho un sacco di funzioni matematiche che inizialmente ho scritto per usare come parte di una classe. Mentre scrivo più codice, però, mi sono reso conto che ho bisogno di queste funzioni matematiche ovunque.

Qual è il posto migliore dove metterli? Diciamo che ho questo:

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

E quando scrivo un'altra lezione, non posso (o almeno non so come) usarla math_function1in quell'altra lezione. Inoltre, ho realizzato che alcune di queste funzioni non sono realmente correlate alla classe A. Sembravano all'inizio, ma ora posso vedere come sono solo funzioni matematiche.

Qual è la buona pratica in questa situazione? In questo momento li ho copiati e incollati nelle nuove classi, che sono sicuro sia la peggiore pratica.


11
Hai imparato la staticparola chiave?
S.Lott

31
In C ++, le funzioni libere sono quasi sempre preferite rispetto alle funzioni membro.
Pubblico,

4
Non esiste una regola che dice che tutto deve essere in una classe. Almeno non in C ++.
martedì

2
Preferirei uno spazio dei nomi a una classe con un sacco di metodi statici
Nick Keighley

Risposte:


71

Il C ++ può avere funzioni non metodiche bene, se non appartengono a una classe non inserirle in una classe, semplicemente inserirle in ambito globale o altro

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

6
+1 Questa è la soluzione più sensata, anche se lo spazio dei nomi extra non sembra necessario.
Pubblico,

1
no non è necessario
jk.

27
Alcuni spazi dei nomi sono utili per ridurre le potenziali collisioni di nomi con altre librerie.
Bill Door,

11
L'uso di uno spazio dei nomi è anche utile perché chiarisce se la chiamata è un metodo o una funzione. ( math_function1(42)potrebbe chiamare un membro della classe corrente; special_math_functions::math_function1(42)sta chiaramente chiamando una funzione indipendente). Detto questo, ::math_function(42)fornisce la stessa chiara ambiguità.
ipeet,

2
Gli spazi dei nomi non sono necessari ma non sono neppure vietati. Da qui il motivo per cui questa risposta, dice // optional. Condire a piacere.
user253751

6

Dipende da come è organizzato il progetto e dal tipo di schemi di progettazione che stai utilizzando, supponendo che si tratti di un codice di utilità rigoroso, hai le seguenti opzioni:

  • Se non devi usare oggetti per qualsiasi cosa, potresti fare qualcosa di semplice come metterli tutti in un file senza un wrapper di classe attorno a loro. Ciò potrebbe avvenire con o senza uno spazio dei nomi, sebbene lo spazio dei nomi sia consigliato per evitare problemi in futuro.
  • Per il C ++ gestito puoi creare una classe statica per contenerli tutti; tuttavia, questo non funziona davvero come una vera classe e la mia comprensione è che si tratta di un anti-pattern C ++.
  • Se non stai usando C ++ gestito, puoi semplicemente fare uso di funzioni statiche per permetterti di accedervi e averle tutte contenute in una singola classe. Questo può essere utile se ci sono anche altre funzioni per le quali si desidera che un oggetto istanziato corretto possa essere anche un anti-pattern.
  • Se vuoi assicurarti che esista solo un'istanza dell'oggetto che contiene le funzioni, puoi usare il Singleton Pattern per una classe di utilità che ti consenta anche una certa flessibilità in futuro dato che ora hai accesso ad attributi non statici. Questo sarà di utilità limitata e si applica davvero solo se hai bisogno di un oggetto per qualche motivo. Le probabilità sono che se lo fai saprai già perché.

Nota che la prima opzione sarà la migliore scommessa e le tre seguenti sono di utilità limitata. Detto questo, tuttavia, potresti riscontrare solo a causa dei programmatori C # o Java che fanno un po 'di lavoro in C ++ o se lavori mai su codice C # o Java in cui l'uso delle classi è obbligatorio.


Perché il voto negativo?
rjzii,

10
Non sono il downvoter, ma probabilmente perché stai consigliando una classe con funzioni statiche o un singleton, mentre le funzioni gratuite andrebbero probabilmente bene in questo caso (e sono accettabili e utili per molte cose in C ++).
Anton Golov,

@AntonGolov - Le funzioni gratuite sono la prima cosa che ho citato nell'elenco. :) Gli altri sono gli approcci più orientati all'OOP per le situazioni in cui hai a che fare con "Tutto deve essere una classe!" ambienti.
rjzii,

9
@Rob Z: Tuttavia, C ++ non è uno di quelli "Tutto deve essere una classe!" ambienti.
David Thornley,

1
Da quando è OOP forzare le funzioni pure in una classe? Sembra più simile a OOP-cargo-cult.
Deduplicatore,

1

Come hai già detto, incollare il codice è la peggior forma di riutilizzo del codice. Se hai funzioni che non appartengono a nessuna delle tue classi o che possono essere utilizzate per diversi scenari, il posto migliore per metterle sarebbe una classe di aiuto o di utilità. Se non usano alcun dato di istanza, possono essere resi statici, quindi non è necessario creare un'istanza della classe di utilità per usarli.

Vedi qui per una discussione sulle funzioni dei membri statici in C ++ nativo e qui per le classi statiche in C ++ gestito. È quindi possibile utilizzare questa classe di utilità ovunque si sarebbe incollato il codice.

In .NET, ad esempio, cose come Min()e Max()vengono fornite come membri statici sulla System.Mathclasse .

Se tutte le funzioni sono la matematica correlate e si sarebbe altrimenti in una gigantesca Mathclasse, si potrebbe desiderare di scomposizione ulteriormente e hanno classi come TrigonometryUtilities, EucledianGeometryUtilitiese così via.

Un'altra opzione sarebbe quella di mettere la funzionalità condivisa in una classe base delle classi che richiedono detta funzionalità. Funziona bene, quando le funzioni nelle domande devono operare su dati di istanza, tuttavia, questo approccio è anche meno flessibile se si desidera evitare l'ereditarietà multipla e attenersi a una sola classe di base, poiché si "utilizza" la propria base classe solo per ottenere l'accesso ad alcune funzionalità condivise.


18
IMHO, le classi di utilità con nient'altro che membri statici sono un anti-pattern in C ++. Stai usando una classe per riprodurre perfettamente il comportamento di uno spazio dei nomi, il che non ha davvero senso.
ipeet,

+1 per menzionare le classi di utilità. Linguaggi come C # richiedono che tutto sia incluso in una classe, quindi è abbastanza comune creare un numero di classi di utilità per vari scopi. L'implementazione di queste classi come Statico rende le utilità ancora più facili da usare ed evita i mal di testa che l'ereditarietà può talvolta creare, in particolare quando le classi di base si gonfiano di codice che può essere utilizzato solo da uno o due discendenti. Tecniche simili possono essere applicate in altre lingue per fornire un contesto significativo per le funzioni di utilità, anziché lasciarle fluttuare nell'ambito globale.
S.Robins,

5
@ S.Robins: Non c'è bisogno di nulla del genere in C ++, puoi semplicemente metterli in uno spazio dei nomi, che ha lo stesso effetto.
DeadMG,

0

Disambiguare il termine "Funzione di aiuto". Una definizione è una funzione di convenienza che usi tutto il tempo solo per fare qualche lavoro. Possono vivere nello spazio dei nomi principale e avere le proprie intestazioni, ecc. L'altra definizione della funzione di supporto è una funzione di utilità per una singola classe o famiglia di classi.

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

Ora isPrinterè disponibile per qualsiasi codice inclusa la sua intestazione, ma print_alignment_pagerichiede una using namespace printer_utils::Xerox;direttiva. Si può anche fare riferimento come

Canon::print_alignment_page();

per essere più chiari.

Il C ++ STL ha lo std::spazio dei nomi che copre quasi tutte le sue classi e funzioni, ma le suddivide categoricamente in oltre 17 diverse intestazioni per consentire al programmatore di togliere i nomi delle classi, i nomi delle funzioni, ecc. Se vogliono scrivere il loro.

In effetti, NON è consigliabile utilizzare using namespace std;in un file di intestazione o, come spesso accade, come prima riga all'interno main(). std::è composto da 5 lettere e spesso sembra un lavoro ingrato prefigurare la funzione che si vuole usare (specialmente std::coute std::endl!) ma serve a uno scopo.

Il nuovo C ++ 11 contiene alcuni spazi dei nomi secondari per servizi speciali come

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

che può essere portato per l'uso.

Una tecnica utile è la composizione dello spazio dei nomi . Uno definisce uno spazio dei nomi personalizzato per contenere gli spazi dei nomi necessari per il proprio .cppfile specifico e usarlo invece di un mucchio di usingistruzioni per ogni cosa in uno spazio dei nomi che potrebbe essere necessario.

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

Questa tecnica limita l'esposizione al tutto std:: namespace( è grande! ) E consente di scrivere codice più pulito per le righe di codice più comuni che le persone scrivono più spesso.


-2

Potresti volerlo inserire in una funzione modello per renderlo disponibile per diversi tipi di numeri interi e / o float:

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

Puoi anche creare tipi personalizzati accurati che rappresentano ad esempio numeri enormi o numeri complessi sovraccaricando gli operatori rilevanti per il tuo tipo personalizzato per renderli compatibili con i modelli.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.