Dove devo inserire funzioni che non sono correlate a una classe?

42

Sto lavorando su un progetto C ++ in cui ho un sacco di funzioni matematiche che ho inizialmente scritto per usare come parte di una classe. Come ho scritto più codice, però, ho capito che ho bisogno di queste funzioni matematiche ovunque.

Dove è il posto migliore per metterli? Diciamo che ho questo:

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

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

Che cosa è una buona pratica in questa situazione? In questo momento li ho copiati incollandoli nelle nuove classi, che sono sicuro sia la peggiore pratica.

    
posta coconut 10.02.2012 - 19:31
fonte

5 risposte

66

C ++ può avere funzioni non di tipo corretto, se non appartengono a una classe non le mettono in una classe, basta metterle in ambito globale o di altro namespace

namespace special_math_functions //optional
{
    int math_function1(int arg)
    {
         //definition 
    }
}
    
risposta data 10.02.2012 - 19:42
fonte
6

Dipende da come è organizzato il progetto e da che tipo di schemi di progettazione stai usando, assumendo che questo sia strettamente codice di utilità hai le seguenti opzioni:

  • Se non è necessario utilizzare oggetti per tutto ciò che si potrebbe fare qualcosa di semplice, basta metterli tutti in un file senza un wrapper di classe. Questo potrebbe essere con o senza uno spazio dei nomi, sebbene lo spazio dei nomi sia consigliato per prevenire eventuali problemi in futuro.
  • Per C ++ gestito puoi creare una classe statica per contenerli tutti; tuttavia, questo non funziona esattamente come una classe reale e la mia comprensione è che si tratta di un anti-pattern C ++.
  • Se non utilizzi il C ++ gestito, puoi semplicemente utilizzare funzioni statiche per permetterti di accedervi e averli tutti contenuti in una singola classe. Questo può essere utile se ci sono anche altre funzioni per le quali si potrebbe desiderare un oggetto istanziato appropriato potrebbe anche essere un anti-pattern.
  • Se vuoi assicurarti che esista solo una istanza dell'oggetto contenente le funzioni, puoi utilizzare il Singleton Pattern per una classe di utilità che ti consente anche una certa flessibilità in futuro, ora che hai accesso agli attributi non statici. Questo sarà di uso limitato e si applica solo se hai bisogno di un oggetto per qualche motivo. Le probabilità sono che se lo fai saprai già perché.

Si noti che la prima opzione sarà la migliore e i seguenti tre sono di utilità limitata. Detto questo, potresti incontrare solo il C # o i programmatori Java che eseguono un lavoro C ++ o se lavori su C # o sul codice Java dove l'uso delle classi è obbligatorio.

    
risposta data 10.02.2012 - 19:37
fonte
1

Come hai già detto, copiare e incollare il codice è la peggiore forma di riutilizzo del codice. Se hai delle funzioni che non appartengono a nessuna delle tue classi o potrebbero essere utilizzate per diversi scenari, il miglior posto per metterli sarebbe un aiuto o una classe di utilità. Se non utilizzano dati di istanza, possono essere resi statici, quindi non è necessario creare un'istanza della classe di utilità per utilizzarli.

Vedi qui per una discussione sulle funzioni dei membri statici in C ++ nativo e qui per le classi statiche in gestione C ++. Potresti quindi utilizzare questa classe di utilità ovunque tu abbia incollato il tuo codice.

In .NET, ad esempio, cose come Min() e Max() vengono fornite come membri statici su System.Math class .

Se tutte le tue funzioni sono correlate alla matematica e dovresti avere una gigantesca classe Math , potresti voler suddividerla ulteriormente e avere classi come TrigonometryUtilities , EucledianGeometryUtilities e così via.

Un'altra opzione sarebbe quella di mettere le funzionalità condivise in una classe base delle classi che richiedono tale funzionalità. Questo funziona bene, quando le funzioni in questione devono operare su dati di istanza, tuttavia, questo approccio è anche meno flessibile se si desidera evitare l'ereditarietà multipla e attenersi solo a una classe base, perché si "consumerebbe" la propria base classe solo per accedere ad alcune funzionalità condivise.

    
risposta data 10.02.2012 - 19:37
fonte
0

Disambigui il termine "Funzione di supporto". Una definizione è una comodità funzione che usi tutto il tempo solo per fare un lavoro. quelli può vivere nel namespace principale e avere le proprie intestazioni, ecc. L'altra definizione della funzione helper è una funzione di utilità per una singola classe o classe.

// 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_page richiede a 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 spazio dei nomi std:: che copre quasi tutto il suo classi e funzioni, ma suddivide categoricamente in più di 17 diverse intestazioni per consentire al codificatore di estrarre i nomi delle classi, i nomi delle funzioni, ecc. se vogliono scrivere da soli.

In effetti, NON è consigliato utilizzare using namespace std; in un file di intestazione o, come spesso accade, come prima riga all'interno di main() . std:: è 5 lettere e spesso sembra un lavoretto per prefigurare la funzione che si vuole usare (specialmente std::cout e std::endl !) ma ha uno scopo.

Il nuovo C ++ 11 ha alcuni sotto-namespace in esso 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 tuo particolare file .cpp e usa quello invece di un gruppo di istruzioni using per ogni cosa in uno spazio dei nomi che potresti aver bisogno.

#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 all'intero std:: namespace ( è grande! ) e consente di scrivere codice più pulito per le linee di codice più comuni che le persone scrivono più spesso.

    
risposta data 25.05.2017 - 06:07
fonte
-2

Potresti metterlo in una funzione template per renderlo disponibile per diversi tipi di numeri interi e / o float:

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

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

    
risposta data 15.01.2015 - 22:35
fonte

Leggi altre domande sui tag