2010-04-06 15 views
13

L'aggiunta di funzionalità a una classe può essere eseguita aggiungendo un metodo o definendo una funzione che accetta un oggetto come primo parametro. La maggior parte dei programmatori che conosco sceglierebbero per la soluzione di aggiungere un metodo di istanza.Scelta tra metodi di istanza e funzioni libere?

Tuttavia, a volte preferisco creare una funzione separata. Ad esempio, nel codice di esempio riportato sotto Area e Diagonal vengono definite come funzioni gratuite anziché metodi. Lo trovo migliore in questo modo perché penso che queste funzioni forniscano miglioramenti piuttosto che funzionalità di base.

Questa è considerata una buona/cattiva pratica? Se la risposta è "dipende", quali sono le regole per decidere tra aggiungere un metodo o definire una funzione separata?

class Rect 
{ 
public: 
    Rect(int x, int y, int w, int h) : 
     mX(x), mY(y), mWidth(w), mHeight(h) 
    { 
    } 

    int x() const { return mX; } 

    int y() const { return mY; } 

    int width() const { return mWidth; } 

    int height() const { return mHeight; } 

private: 
    int mX, mY, mWidth, mHeight; 
}; 


int Area(const Rect & inRect) 
{ 
    return inRect.width() * inRect.height(); 
} 


float Diagonal(const Rect & inRect) 
{ 
    return std::sqrt(std::pow(static_cast<float>(inRect.width()), 2) + std::pow(static_cast<float>(inRect.height()), 2)); 
} 
+0

Una nota a margine, l'uso di Pow con una potenza costante di due è estremamente inefficiente rispetto alla semplice ortografia di x * x + y * y + z * z. –

+0

@ Mark B: Grazie, non lo sapevo. – StackedCrooked

+1

Vedere il classico _ [Come le funzioni non membro migliorano l'incapsulamento] (http://drdobbs.com/cpp/184401197) _. ("Quando si pensa all'incapsulamento, è necessario pensare a funzioni non membro"). Vedere anche http://stackoverflow.com/q/1692084/140719. – sbi

risposta

14

GoTW #84 ha esaminato questa domanda rispetto a std::string. Tendeva piuttosto all'idea opposta: la maggior parte delle funzioni dovrebbe essere globale, a meno che non abbiano davvero bisogno di essere membri.

Direi che il C++ più moderno tende alla stessa idea. Se guardi attraverso Boost, ad esempio, un bel po 'è fatto con funzioni libere.

+4

Amo questa citazione da Scott Meyers contenuta nell'articolo: > Comincerò con la battuta finale: se si sta scrivendo una funzione che può essere implementato sia come membro o come un non-amico non membro, è dovrebbe preferire implementarlo come una funzione non membro. Quella decisione accresce l'incapsulamento di classe. Quando pensi all'incapsulamento, dovresti pensare a funzioni non associate. –

+2

Il mio solo probabilmente con questo è l'inquinamento dello spazio dei nomi. È float Geometry :: Area (const Geometry :: Rect & r) meglio di Geometry :: Rect :: Area()? Non vuoi veramente che la funzione di area libera si trovi nello spazio dei nomi globale, quindi devi inserire uno spazio dei nomi. E a quel punto sembra una funzione di classe statica. – jmucchiello

+2

@jmucchiello: Sì, la maggior parte delle funzioni gratuite dovrebbe essere in spazi dei nomi. Non penso a questo come a qualcosa che assomiglia a funzioni di membri statici - Sono abituato a 'x :: y' che significa" funzione y nel namespace x ". –

1

Direi che dipende da quali sono i tuoi obiettivi. Se la funzione è una funzione membro, puoi utilizzare. e -> notazione con esso, ed è meglio integrato con la classe. Inoltre consente il polimorfismo. Se vuoi che la tua funzione sia virtuale, deve essere una funzione membro.

Tuttavia, esiste una scuola di pensiero che è meglio incapsulare per rendere le funzioni membro funzioni solo se necessario. Quindi, se la funzione in realtà non hanno bisogno di accedere alle variabili membro (presumibilmente perché può fare quello che deve fare con solo le funzioni pubbliche), allora si rendono una funzione separata, che non è un membro della classe. In questo modo non può pasticciare con gli interni nemmeno per caso.

Un'altra considerazione è che se non è una funzione membro, ci si deve preoccupare se altri tipi possono o devono essere espressi sul tipo che è ora il primo parametro della funzione. A volte ciò è desiderabile e talvolta no. Se si tratta di una funzione membro, non è un problema (o un'opportunità, a seconda di cosa si sta tentando di fare).

Personalmente, non mi piacciono le funzioni che sono separate dalla classe, ma spesso sei costretto a scrivere le funzioni in questo modo quando non hai il controllo sulla classe, o non puoi cambiarla per compatibilità ragioni o quant'altro.

In realtà, penso che si tratti di ciò che è più importante per te e di ciò che stai davvero cercando di fare. Penso che avere la funzione di essere una parte della classe rende più facile da usare e più orientata agli oggetti, ma è meglio se si encpasulation limitare quanto ha accesso ai membri privati ​​della classe.

2

Se si desidera utilizzarlo con altri, le classi non correlate, dovrebbe essere una funzione libera con sostituzioni.

Ad esempio, è possibile ignorare float Area(Hexagon const &) e float Diagonal(Hexagon const &). x, y, width, e height però significare cose diverse per le diverse forme.

Le astratte classi di base (o metodi di omonimia non correlati) sono un'altra soluzione, ma le funzioni libere sono estendibili dall'utente in modo che gli utenti ottengano effettivamente un'interfaccia più uniforme una volta aggiunto il proprio materiale.

Evitare funzioni libere con nomi ambigui, in modo che le funzioni non correlate non diventino sovrascritture "concorrenti".