Le classi di utilità con nient'altro che membri statici sono un anti-pattern in C ++?


39

La domanda su dove dovrei mettere funzioni che non sono correlate a una classe ha suscitato un dibattito sul fatto che abbia senso in C ++ combinare funzioni di utilità in una classe o semplicemente farle esistere come funzioni libere in uno spazio dei nomi.

Vengo da uno sfondo C # in cui quest'ultima opzione non esiste e quindi naturalmente tendo a utilizzare le classi statiche nel piccolo codice C ++ che scrivo. La risposta più votata su questa domanda, nonché diversi commenti, tuttavia affermano che le funzioni libere devono essere preferite, anche suggerendo che le classi statiche sono un anti-schema. Perché è così in C ++? Almeno in superficie, i metodi statici su una classe sembrano indistinguibili dalle funzioni libere in uno spazio dei nomi. Perché quindi la preferenza per quest'ultima?

Le cose sarebbero diverse se la raccolta di funzioni di utilità richiedesse alcuni dati condivisi, ad esempio una cache che si potrebbe archiviare in un campo statico privato?


Sembra un po 'come l'antipasto "decomposizione funzionale".
user281377,

7
Risposta breve: non è necessario un corso per concludere queste funzioni. Le funzioni gratuite si adattano molto meglio al tuo compito piuttosto che scricchiolarle in un costrutto pseudo-OO, che è una soluzione alternativa che ti serve solo in linguaggi "puramente OO".
Chris dice di reintegrare Monica l'

Risposte:


39

Immagino di rispondere che dovremmo confrontare le intenzioni di entrambe le classi e gli spazi dei nomi. Secondo Wikipedia:

Classe

Nella programmazione orientata agli oggetti, una classe è un costrutto che viene utilizzato come modello per creare istanze di se stesso, denominate istanze di classe, oggetti di classe, oggetti di istanza o semplicemente oggetti. Una classe definisce i membri costituenti che consentono a queste istanze di classe di avere stato e comportamento. I membri del campo dati (variabili membro o variabili di istanza) consentono a un oggetto classe di mantenere lo stato. Altri tipi di membri, in particolare metodi, abilitano il comportamento di un oggetto classe. Le istanze di classe sono del tipo di classe associata.

Spazio dei nomi

In generale, uno spazio dei nomi è un contenitore che fornisce il contesto per gli identificatori (nomi, termini tecnici o parole) che contiene e consente la chiarimento delle ambiguità degli identificatori omonimi che risiedono in spazi dei nomi diversi.

Ora, cosa stai cercando di ottenere inserendo le funzioni in una classe (staticamente) o in uno spazio dei nomi? Scommetto che la definizione di uno spazio dei nomi descrive meglio le tue intenzioni: tutto ciò che vuoi è un contenitore per le tue funzioni. Non hai bisogno di nessuna delle funzionalità descritte nella definizione della classe. Si noti che le prime parole della definizione della classe sono " Nella programmazione orientata agli oggetti ", ma non c'è nulla orientato agli oggetti in una raccolta di funzioni.

Probabilmente ci sono anche ragioni tecniche, ma poiché qualcuno che viene da Java e cerca di orientarmi nel linguaggio multi-paradigma che è il C ++, la risposta più ovvia per me è: perché non abbiamo bisogno di OO per raggiungere questo obiettivo.


1
+1 per "Non abbiamo bisogno di OO per raggiungere questo obiettivo"
Ixrec

Sono d'accordo con questo come un fan di OO, ma immagino che ci siano alcune situazioni in cui l'uso di una classe All-static è l'unica soluzione, ad esempio, la sostituzione di uno spazio dei nomi, poiché non è possibile dichiarare uno spazio dei nomi nidificato all'interno una classe.
Wael Boutglay

26

Sarei molto cauto nel chiamarlo anti-schema. Gli spazi dei nomi sono generalmente preferiti, ma poiché non esistono modelli di spazi dei nomi e gli spazi dei nomi non possono essere passati come parametri del modello, l'uso di classi con nient'altro che membri statici è abbastanza comune.


3
Le altre risposte hanno superato l'ultima riga dell'OP. Gli spazi dei nomi sono praticamente ambiti denominati, quindi se è necessario il controllo dell'accesso, le classi sono l'unico strumento disponibile.
cmannett85,

2
A dire il vero @ cbamber85, avevo messo quella riga solo dopo aver letto le prime due risposte.
PersonalNexus,

@PersonalNexus Ah, abbastanza giusto, non me ne sono reso conto!
cmannett85,

10
@ cbamber85: non è del tutto vero. Otterresti un'incapsulamento ancora migliore inserendo funzioni "private" nel file di implementazione, in modo che gli utenti non possano vedere nemmeno la dichiarazione privata.
UncleBens,

2
I modelli +1 sono in effetti un punto in cui gli spazi dei nomi mancano ancora di funzionalità.
Chris dice di reintegrare Monica l'

13

Ai tempi avevo bisogno di seguire un corso FORTRAN. All'epoca conoscevo altre lingue imperative, quindi ho pensato che avrei potuto fare FORTRAN senza studiare molto. Quando ho girato i miei primi compiti, il professore me lo ha restituito e mi ha chiesto di rifarlo: ha detto che i programmi Pascal scritti nella sintassi FORTRAN non contano come invii validi.

Questione simile è in gioco qui: l'uso di classi statiche per ospitare funzioni di utilità in C ++ è un linguaggio estraneo a C ++.

Come hai detto nella tua domanda, usare le classi statiche per le funzioni di utilità in C # è una questione di necessità: le funzioni indipendenti non sono semplicemente un'opzione in C #. Il linguaggio necessario per sviluppare un modello per consentire ai programmatori di definire funzioni indipendenti in qualche altro modo, vale a dire all'interno di classi di utilità statiche. Questo era un trucco di Java preso parola per parola: ad esempio, java.lang.Math e System.Math of .NET sono quasi isomorfi.

C ++, tuttavia, offre spazi dei nomi, una funzione diversa per raggiungere lo stesso obiettivo e lo utilizza attivamente nell'implementazione della sua libreria standard. L'aggiunta di un ulteriore livello di classi statiche non è solo inutile, ma anche in qualche modo controintuitivo per i lettori senza background C # o Java. In un certo senso, stai introducendo una "traduzione in prestito" nella lingua per qualcosa che può essere espresso in modo nativo.

Quando le tue funzioni devono condividere i dati, la situazione è diversa. Poiché le funzioni non sono più non correlate , il modello Singleton diventa il modo preferito di soddisfare questo requisito.


6
+1, tranne per la raccomandazione di singoli. Sono Non preferiti in C ++! Le funzioni possono essere in una classe se hanno bisogno di stato di condivisione, ma le classi non dovrebbero essere single a meno che davvero, davvero bisogno di essere. E di solito no.
Maxpm

@Maxpm Non fraintendetemi, singleton è preferito solo alle variabili statiche globali. A parte questo, non c'è nulla di "preferito" in questo.
dasblinkenlight,

2
C'è questo buon articolo, Singletons: Risolvere problemi che non sapevi che non avresti mai avuto dal 1995 . Riassumendo, afferma che associare l'istanza ben nota alla classe stessa limita inutilmente la tua flessibilità e non ti dà niente. Il più delle volte hanno solo una classe e hanno un'istanza condivisa da qualche parte, ma non renderla singleton.
Jan Hudec,

Non sono sicuro che "idioma straniero" sia il termine giusto. È un linguaggio molto comune. Penso che alcuni team di programmazione stiano usando l'e2 di Stroustrup ...
Nick Keighley

10

Forse devi chiederti perché vorresti una classe completamente statica?

L'unica ragione a cui riesco a pensare è che altri linguaggi (Java e C #) che sono molto "tutto è una classe" li richiedono. Questi linguaggi non possono affatto creare funzioni di livello superiore, quindi hanno inventato un trucco per mantenerli, e questa era la funzione membro statico. Sono un po 'una soluzione alternativa, ma C ++ non ha bisogno di una cosa del genere, puoi creare direttamente nuove funzioni indipendenti di alto livello.

Se hai bisogno di funzioni che operano su un determinato elemento di dati, allora ha senso raggrupparli in una classe che contiene i dati, ma poi, questi smettono di essere funzioni e iniziano a essere membri di quella classe che operano sui dati della classe.

Se hai una funzione che non opera su un particolare tipo di dati (io uso la parola qui poiché le classi sono modi per definire nuovi tipi di dati), allora è davvero un anti-pattern spingerli in una classe, per niente meno che scopi semantici.


10
Anzi, direi che la "classe con tutti i membri statici" in Java è in realtà un trucco per aggirare una limitazione di Java.
Charles Salvia,

@CharlesSalvia: ero educato :) Ho la stessa brutta sensazione che main () faccia parte di una classe java, anche se capisco perché lo hanno fatto.
gbjbaanb,

Questo in realtà sembra fornire la risposta al motivo per cui vorresti una classe completamente statica. O ti fraintendo? Ad esempio, ho bisogno di contenere una variabile il cui valore può cambiare che viene letto da una classe (che intendo archiviare nella ROM) e scritto da un'altra classe (che sarà nella RAM). Il semplice posizionamento in una posizione nota del ram non è sufficiente: avvolgerlo (e i relativi accessori) in una classe mi consente di ottimizzare il controllo degli accessi. Praticamente ciò che descrivi nel tuo secondo paragrafo.
iheanyi,

5

Almeno in superficie, i metodi statici su una classe sembrano indistinguibili dalle funzioni libere in uno spazio dei nomi.

In altre parole, avere una classe anziché uno spazio dei nomi non ha vantaggi.

Perché quindi la preferenza per quest'ultima?

Per prima cosa ti risparmia di scrivere staticsempre, anche se questo è probabilmente un vantaggio piuttosto minore.

Il vantaggio principale è che è lo strumento meno potente per il lavoro. Le classi possono essere utilizzate per creare oggetti, possono essere utilizzate come tipo di variabili o come argomenti modello. Nessuna di queste sono le funzionalità che desideri per la tua raccolta di funzioni. Quindi è preferibile utilizzare uno strumento che non ha queste caratteristiche, in modo che la classe non possa essere utilizzata in modo accidentale.

A seguito di ciò, l'utilizzo di uno spazio dei nomi rende immediatamente chiaro a tutti gli utenti del codice che si tratta di una raccolta di funzioni e non di un progetto da cui creare oggetti.


5

... diversi commenti tuttavia affermano che le funzioni libere sono da preferire, anche suggerendo che le classi statiche fossero un anti-schema. Perché è così in C ++? Almeno in superficie, i metodi statici su una classe sembrano indistinguibili dalle funzioni libere in uno spazio dei nomi. Perché quindi la preferenza per quest'ultima?

Una classe completamente statica svolgerà il lavoro, ma è come guidare un camion semi di 53 piedi al supermercato per patatine e salsa quando una berlina a quattro porte lo farà (cioè, è eccessiva). Le lezioni hanno una piccola quantità di spese generali aggiuntive e la loro esistenza potrebbe dare a qualcuno l'impressione che un'istanza possa essere una buona idea. Le funzioni gratuite offerte da C ++ (e C, dove tutte le funzioni sono gratuite) non lo fanno; sono solo funzioni e nient'altro.

Le cose sarebbero diverse se la raccolta di funzioni di utilità richiedesse alcuni dati condivisi, ad esempio una cache che si potrebbe archiviare in un campo statico privato?

Non proprio. Lo scopo originale di staticin C (e in seguito C ++) era offrire uno storage persistente utilizzabile a qualsiasi livello di ambito:

int counter() {
    static int value = 0;
    value++;
}

È possibile portare l'ambito al livello di un file, il che lo rende visibile a tutti gli ambiti più stretti ma non al di fuori del file:

static int value = 0;

int up() { value++; }
int down() { value--; }

Un membro di classe statica privato in C ++ ha lo stesso scopo ma è limitato all'ambito di una classe. La tecnica usata counter()nell'esempio sopra funziona anche con i metodi C ++ ed è qualcosa che consiglierei davvero di fare se la variabile non ha bisogno di essere visibile all'intera classe.


Suppongo che un gruppo di funzioni di utilità non correlate che non condividono dati dovrebbe essere raggruppato meglio in uno spazio dei nomi. Ma se si dispone di un gruppo di funzioni di utilità strettamente collegate che richiedono l'accesso a dati condivisi e forse un controllo di accesso, una classe statica è la scelta migliore. Una statica all'interno di una funzione non è rientrante. Utilizzando un modulo globale ... leggermente migliore in questo contesto, ma continuo a pensare che una classe completamente statica sia la soluzione migliore se hai i requisiti che ho descritto.
iheanyi,

Se condividono dati potrebbero essere migliori come classe senza metodi statici?
Nick Keighley,

@NickKeighley Dipende da chi vince il dibattito sulla possibilità di creare un'istanza e passarla a tutto ciò che ne ha bisogno o semplicemente renderla statica in classe.
Blrfl,

1

Se una funzione non mantiene uno stato ed è rientrante, non sembra avere molto senso spingerla all'interno di una classe (a meno che non sia forzata dalla lingua). Se la funzione mantiene un certo stato (ad es. Può essere resa thread-safe solo tramite un mutex statico), allora i metodi statici su una classe sembrano appropriati.


1

Gran parte del discorso sull'argomento qui ha un senso, anche se c'è qualcosa di molto fondamentale nel C ++ che rende namespacesed classes / structs molto diversi.

Le classi statiche (classi in cui tutti i membri sono statici e la classe non verrà mai istanziata) sono esse stesse oggetti. Non sono semplicemente un namespaceper contenere funzioni.

La meta-programmazione di modelli ci consente di utilizzare una classe statica come oggetto di compilazione.

Considera questo:

template<typename allocator_type> class allocator
{
public:
    inline static void* allocate(size_t size)
    {
        return allocator_type::template allocate(size);
    }
    inline static void release(void* p)
    {
        allocator_type::template release(p);
    }
};

Per usarlo abbiamo bisogno di funzioni contenute all'interno di una classe. Uno spazio dei nomi non funzionerà qui. Ritenere:

class mallocator
{
    inline static void* allocate(size_t size)
    {
        return std::malloc(size);
    }
    inline static void release(void* p)
    {
        return std::free(p);
    }
};

Ora per usarlo:

using my_allocator = allocator<mallocator>;

void* p = my_allocator::allocate(1024);
...
my_allocator::release(p);

Fintanto che un nuovo allocatore espone una allocatee una releasefunzione compatibile, passare a un nuovo allocatore è facile.

Questo non può essere ottenuto con gli spazi dei nomi.

Hai sempre bisogno di funzioni per far parte di una classe? No.

L'uso di una classe statica è un anti-pattern? Dipende dal contesto.

Le cose sarebbero diverse se la raccolta di funzioni di utilità richiedesse alcuni dati condivisi, ad esempio una cache che si potrebbe archiviare in un campo statico privato?

In tal caso, è probabile che ciò che stai cercando di ottenere sia meglio servito attraverso la programmazione orientata agli oggetti.


L'uso della parola chiave inline è qui ridondante poiché i metodi definiti in una definizione di classe sono implicitamente in linea. Vedi en.cppreference.com/w/cpp/language/inline .
Trevor

1

Come diceva notoriamente John Carmack:

"A volte, l'implementazione elegante è solo una funzione. Non un metodo. Non una classe. Non un framework. Solo una funzione."

Penso che riassuma piuttosto bene il punto. Perché dovresti renderlo forzatamente una classe se chiaramente non è una classe? Il C ++ ha il lusso di poter effettivamente utilizzare le funzioni; in Java, tutto è un metodo. Quindi hai bisogno di classi di utilità e molte di esse.

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.