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

38

La domanda dove dovrei inserire funzioni che non sono correlate a una classe ha suscitato qualche discussione sul fatto che abbia senso in C ++ combinare funzioni di utilità in una classe o semplicemente farle esistere come funzioni libere in un namespace.

Vengo da uno sfondo C # in cui l'ultima opzione non esiste e quindi tende naturalmente ad usare le classi statiche nel piccolo codice C ++ che scrivo. La risposta più votata su tale questione, oltre a diversi commenti, afferma tuttavia che sono da preferire le funzioni libere, anche suggerendo che le classi statiche erano un anti-modello. Perché è così in C ++? Almeno in superficie, i metodi statici su una classe sembrano indistinguibili dalle funzioni libere in uno spazio dei nomi. Perché dunque la preferenza per quest'ultimo?

Le cose sarebbero diverse se la raccolta di funzioni di utilità avesse bisogno di alcuni dati condivisi, ad es. una cache si può memorizzare in un campo statico privato?

    
posta PersonalNexus 11.02.2012 - 11:24
fonte

9 risposte

37

Suppongo di dover rispondere che dovremmo confrontare le intenzioni di entrambe le classi e degli spazi dei nomi. Secondo Wikipedia:

Class

In object-oriented programming, a class is a construct that is used as a blueprint to create instances of itself – referred to as class instances, class objects, instance objects or simply objects. A class defines constituent members which enable these class instances to have state and behavior. Data field members (member variables or instance variables) enable a class object to maintain state. Other kinds of members, especially methods, enable a class object's behavior. Class instances are of the type of the associated class.

Spazio dei nomi

In general, a namespace is a container that provides context for the identifiers (names, or technical terms, or words) it holds, and allows the disambiguation of homonym identifiers residing in different namespaces.

Ora, cosa stai cercando di ottenere mettendo le funzioni in una classe (staticamente) o in uno spazio dei nomi? Scommetto che la definizione di uno spazio dei nomi descrive meglio la tua intenzione: tutto quello che vuoi è un contenitore per le tue funzioni. Non hai bisogno di nessuna delle funzionalità descritte nella definizione della classe. Nota che le prime parole della definizione della classe sono " In programmazione orientata agli oggetti ", ma non c'è nulla di orientato agli oggetti su un insieme di funzioni.

Ci sono probabilmente anche ragioni tecniche, ma come qualcuno che proviene da Java e sta cercando di capire il linguaggio multi-paradigma che è C ++, la risposta più ovvia a me è: Perché non abbiamo bisogno di OO per raggiungere questo obiettivo .

    
risposta data 11.02.2012 - 11:54
fonte
24

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

    
risposta data 11.02.2012 - 12:35
fonte
13

Nel giorno in cui avevo bisogno di prendere una classe FORTRAN. A quel tempo conoscevo altre lingue imperative, quindi ho pensato che potevo fare FORTRAN senza studiare molto. Quando ho consegnato i miei primi compiti a casa, il professore me lo ha restituito e ha chiesto di rifarlo: ha detto che i programmi Pascal scritti nella sintassi FORTRAN non contano come invii validi.

Un problema simile è qui in gioco: l'uso di classi statiche per ospitare funzioni di utilità in C ++ è un idioma 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 che consenta ai programmatori di definire funzioni indipendenti in un altro modo, ovvero all'interno di classi di utilità statiche. Questo è stato un trucco Java preso parola per parola: ad esempio, java.lang .Math e System.Math di .NET sono quasi isomorfi.

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

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

    
risposta data 11.02.2012 - 14:51
fonte
10

Forse hai bisogno di chiedere perché vorresti una classe completamente statica?

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

Se hai bisogno di funzioni che operano su un dato specifico, allora ha senso raggrupparle in una classe che contiene i dati, ma poi, smettono di essere funzioni e iniziano ad essere membri di quella classe che opera sulla classe ' dati.

Se hai una funzione che non funziona su un particolare tipo di dati (io uso la parola qui come classi sono modi per definire nuovi tipi di dati) allora è davvero un anti-pattern per inserirli in una classe, per nientemeno che a scopi semantici.

    
risposta data 11.02.2012 - 15:28
fonte
5

At least on the surface, static methods on a class seem indistinguishable from free functions in a namespace.

In altre parole avere una classe invece di uno spazio dei nomi non ha vantaggi.

Why thus the preference for the latter?

Per prima cosa ti risparmia digitando static per tutto il tempo, anche se questo è probabilmente un vantaggio piuttosto secondario.

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 del modello. Nessuna di queste è funzionalità che desideri per la tua collezione di funzioni. Quindi è preferibile utilizzare uno strumento che non ha queste caratteristiche, in modo che la classe non possa essere utilizzata in modo errato.

Seguendo che l'uso di un namespace rende immediatamente chiaro a tutti gli utenti del tuo codice che questa è una raccolta di funzioni e non un progetto per creare oggetti da.

    
risposta data 11.02.2012 - 12:01
fonte
5

... several comments however say that free functions are to be preferred, even suggesting static classes were an anti-pattern. Why is that so in C++? At least on the surface, static methods on a class seem indistinguishable from free functions in a namespace. Why thus the preference for the latter?

Una classe completamente statica farà il suo lavoro, ma è come guidare un camion semi di 53 piedi al supermercato per patatine e salsa quando una berlina a quattro porte farà (cioè, è eccessivo). Le lezioni hanno una piccola quantità di spese generali aggiuntive e la loro esistenza potrebbe dare a qualcuno l'impressione che l'istanziazione di una persona 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.

Would things be different, if the collection of utility functions needed some shared data, e.g. a cache one could store in a private static field?

Non proprio. Lo scopo originale di static in C (e in seguito C ++) era di offrire una memoria persistente utilizzabile a qualsiasi livello di ambito:

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

Puoi estendere l'ambito al livello di un file, che lo rende visibile a tutti gli ambiti più ristretti ma non al di fuori del file:

static int value = 0;

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

Un membro di classe statico privato in C ++ ha lo stesso scopo ma è limitato all'ambito di una classe. La tecnica utilizzata nell'esempio counter() sopra funziona anche all'interno dei metodi C ++ ed è qualcosa che raccomando di fare se la variabile non deve essere visibile all'intera classe.

    
risposta data 11.02.2012 - 13:38
fonte
1

Se una funzione non mantiene uno stato ed è rientrante, sembra che non ci sia molto da fare per spingerlo all'interno di una classe (a meno che non sia forzato dal linguaggio). Se la funzione mantiene uno stato, (ad esempio può essere resa sicura per il thread solo mediante un mutex statico), i metodi statici su una classe sembrano appropriati.

    
risposta data 16.02.2012 - 19:51
fonte
1

Come ha detto famoso John Carmack:

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

Penso che riassuma tutto sommato. Perché dovresti renderlo forzatamente una classe se non è chiaramente una classe? C ++ ha il lusso che puoi effettivamente usare le funzioni; in Java, tutto è un metodo. Allora hai bisogno di classi di utilità, e molte di esse.

    
risposta data 31.12.2017 - 18:26
fonte
0

Gran parte del discorso sull'argomento qui ha senso, anche se c'è qualcosa di molto fondamentale in C ++ che rende namespaces e class es / struct s molto diversi.

Le classi statiche (classi in cui tutti i membri sono statici e la classe non verrà mai creata un'istanza) sono essi stessi oggetti. Non sono semplicemente un namespace per contenere funzioni.

La meta-programmazione del modello ci consente di utilizzare una classe statica come oggetto in fase 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 in una classe. Un namespace non funzionerà qui. Prendere in considerazione:

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);

Finché un nuovo allocatore espone una funzione allocate e release compatibile, passare a un nuovo allocatore è facile.

Questo non può essere ottenuto con namespace.

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

Sta usando una classe statica un anti-pattern? Dipende dal contesto.

Would things be different, if the collection of utility functions needed some shared data, e.g. a cache one could store in a private static field?

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

    
risposta data 13.02.2015 - 23:45
fonte

Leggi altre domande sui tag