Come si crea una classe statica in C ++?


264

Come si crea una classe statica in C ++? Dovrei essere in grado di fare qualcosa del tipo:

cout << "bit 5 is " << BitParser::getBitAt(buffer, 5) << endl;

Supponendo di aver creato la BitParserclasse. Come sarebbe la BitParserdefinizione della classe?


198
Sì. Ai vecchi tempi, abbiamo appena chiamato quella "funzione". Ragazzi oggi con i vostri folli "spazi dei nomi". Ehi, vattene dal mio prato! <pugno tremante>
Vagrant

7
@Vagrant una funzione all'interno di uno spazio dei nomi è ancora una funzione. Una funzione che appartiene a una classe è chiamata metodo. Se è un metodo statico, lo invochi in modo simile come se fosse una funzione all'interno di uno spazio dei nomi.

48
5 anni dopo e ora mi schierò con @Vagrant. Che domanda stupida!
Andrew

16
9 anni dopo e ora ho il mio linguaggio di programmazione che non ha nemmeno lezioni: ziglang.org
andrewrk,

1
Le classi simili a contenitori IMO (che hanno solo metodi statici) sono utili in alcuni casi.
AarCee,

Risposte:


272

Se stai cercando un modo per applicare la parola chiave "statica" a una classe, come è possibile ad esempio in C #, allora non potrai farlo senza usare il C ++ gestito.

Ma l'aspetto del tuo esempio, devi solo creare un metodo statico pubblico sul tuo oggetto BitParser. Così:

BitParser.h

class BitParser
{
 public:
  static bool getBitAt(int buffer, int bitIndex);

  // ...lots of great stuff

 private:
  // Disallow creating an instance of this object
  BitParser() {}
};

BitParser.cpp

bool BitParser::getBitAt(int buffer, int bitIndex)
{
  bool isBitSet = false;
  // .. determine if bit is set
  return isBitSet;
}

È possibile utilizzare questo codice per chiamare il metodo allo stesso modo del codice di esempio.

Spero che aiuti! Saluti.


2
GU, hai un errore di sintassi . La parola chiave statica deve essere utilizzata solo nella definizione della classe e non nella definizione del metodo.
andrewrk,

90
Per chiarire le tue intenzioni in questo approccio puoi anche usare un costruttore privato. private: BitParser() {}Ciò impedirà a chiunque di creare istanze.
Danvil,

7
La sicurezza del thread @MoatazElmasry è un problema quando si condivide lo stato. Nell'implementazione sopra non c'è stato condiviso, quindi non ci possono essere problemi con la sicurezza dei thread ... a meno che tu non sia abbastanza stupido da usare la statica all'interno di quelle funzioni. Quindi sì, il codice sopra è thread-safe, mantieni lo stato persistente fuori dalle tue funzioni e sei a posto.
GU.

@MoatazElmasry errato. Due thread non possono modificare variabili locali non statiche in una funzione statica.
GU.

12
Se C ++ 11, direi che è meglio BitParser() = delete;trasmettere correttamente l'intenzione di rimuovere il costruttore (non solo nasconderlo come private).
fenice,

247

Considera la soluzione di Matt Price .

  1. In C ++, una "classe statica" non ha significato. La cosa più vicina è una classe con solo metodi e membri statici.
  2. L'uso di metodi statici ti limiterà.

Quello che vuoi è, espresso in semantica C ++, per mettere la tua funzione (perché è una funzione) in uno spazio dei nomi.

Modifica 2011-11-11

Non esiste una "classe statica" in C ++. Il concetto più vicino sarebbe una classe con solo metodi statici. Per esempio:

// header
class MyClass
{
   public :
      static void myMethod() ;
} ;

// source
void MyClass::myMethod()
{
   // etc.
}

Ma devi ricordare che le "classi statiche" sono hack nei linguaggi simili a Java (es. C #) che non sono in grado di avere funzioni non membri, quindi devono invece spostarle all'interno delle classi come metodi statici.

In C ++, ciò che vuoi veramente è una funzione non membro che dichiarerai in uno spazio dei nomi:

// header
namespace MyNamespace
{
   void myMethod() ;
}

// source
namespace MyNamespace
{
   void myMethod()
   {
      // etc.
   }
}

Perché?

In C ++, lo spazio dei nomi è più potente delle classi per il modello "Metodo statico Java", perché:

  • i metodi statici hanno accesso ai simboli privati ​​delle classi
  • i metodi statici privati ​​sono ancora visibili (se inaccessibili) a tutti, il che viola in qualche modo l'incapsulamento
  • i metodi statici non possono essere dichiarati in avanti
  • i metodi statici non possono essere sovraccaricati dall'utente della classe senza modificare l'intestazione della libreria
  • non c'è nulla che possa essere fatto con un metodo statico che non può essere fatto meglio di una funzione non membro (possibilmente amico) nello stesso spazio dei nomi
  • gli spazi dei nomi hanno la loro semantica (possono essere combinati, possono essere anonimi, ecc.)
  • eccetera.

Conclusione: non copiare / incollare il modello di Java / C # in C ++. In Java / C #, il modello è obbligatorio. Ma in C ++, è cattivo stile.

Modifica 10/06/2010

C'è stato un argomento a favore del metodo statico perché a volte, è necessario utilizzare una variabile membro privata statica.

Non sono abbastanza d'accordo, come mostrato di seguito:

La soluzione "Membro privato statico"

// HPP

class Foo
{
   public :
      void barA() ;
   private :
      void barB() ;
      static std::string myGlobal ;
} ;

Innanzitutto, myGlobal si chiama myGlobal perché è ancora una variabile privata globale. Uno sguardo alla fonte CPP chiarirà che:

// CPP
std::string Foo::myGlobal ; // You MUST declare it in a CPP

void Foo::barA()
{
   // I can access Foo::myGlobal
}

void Foo::barB()
{
   // I can access Foo::myGlobal, too
}

void barC()
{
   // I CAN'T access Foo::myGlobal !!!
}

A prima vista, il fatto che la barra delle funzioni libere C non riesca ad accedere a Foo :: myGlobal sembra una buona cosa dal punto di vista dell'incapsulamento ... È bello perché qualcuno che guarda l'HPP non sarà in grado (se non ricorrendo al sabotaggio) di accedere foo :: myglobal.

Ma se lo guardi da vicino, scoprirai che si tratta di un colossale errore: non solo la tua variabile privata deve ancora essere dichiarata nell'HPP (e quindi, visibile a tutto il mondo, nonostante sia privata), ma devi dichiarare nella stessa HPP tutte le funzioni (come in TUTTI) che saranno autorizzate ad accedervi !!!

Quindi usare un membro statico privato è come camminare fuori nel nudo con l'elenco dei tuoi amanti tatuato sulla pelle: nessuno è autorizzato a toccare, ma tutti sono in grado di sbirciare. E il bonus: tutti possono avere i nomi di quelli autorizzati a giocare con i tuoi privati.

private anzi ... MrGreen

La soluzione "Spazi dei nomi anonimi"

Gli spazi dei nomi anonimi avranno il vantaggio di rendere le cose private davvero private.

Innanzitutto, l'intestazione HPP

// HPP

namespace Foo
{
   void barA() ;
}

Solo per essere sicuri di aver osservato: non esiste una dichiarazione inutile di barB né myGlobal. Ciò significa che nessuno che legge l'intestazione sa cosa si nasconde dietro la barraA.

Quindi, il CPP:

// CPP
namespace Foo
{
   namespace
   {
      std::string myGlobal ;

      void Foo::barB()
      {
         // I can access Foo::myGlobal
      }
   }

   void barA()
   {
      // I can access myGlobal, too
   }
}

void barC()
{
   // I STILL CAN'T access myGlobal !!!
}

Come puoi vedere, come la cosiddetta dichiarazione "classe statica", fooA e fooB sono ancora in grado di accedere a myGlobal. Ma nessun altro può. E nessun altro al di fuori di questo CPP sa che esistono fooB e myGlobal!

A differenza della "classe statica" che cammina sul nudo con la sua rubrica tatuata sulla pelle, lo spazio dei nomi "anonimo" è completamente vestito , il che sembra piuttosto incapsulato AFAIK.

Importa davvero?

A meno che gli utenti del vostro codice sono sabotatori (Ti lascerò, come esercizio, scopri come si può accedere alla parte privata di una classe pubblica utilizzando un hack comportamento non definito sporca ...), ciò che è privateè private, anche se è visibile nella privatesezione di una classe dichiarata in un'intestazione.

Tuttavia, se è necessario aggiungere un'altra "funzione privata" con accesso al membro privato, è comunque necessario dichiararlo a tutto il mondo modificando l'intestazione, che è un paradosso per quanto mi riguarda: se cambio l'implementazione di il mio codice (la parte CPP), quindi l'interfaccia (la parte HPP) NON dovrebbe cambiare. Citando Leonidas: " Questo è ENCAPSULATION! "

Modifica 20/09/2014

Quando i metodi statici delle classi sono effettivamente migliori degli spazi dei nomi con funzioni non membro?

Quando è necessario raggruppare le funzioni e alimentare quel gruppo in un modello:

namespace alpha
{
   void foo() ;
   void bar() ;
}

struct Beta
{
   static void foo() ;
   static void bar() ;
};

template <typename T>
struct Gamma
{
   void foobar()
   {
      T::foo() ;
      T::bar() ;
   }
};

Gamma<alpha> ga ; // compilation error
Gamma<Beta> gb ;  // ok
gb.foobar() ;     // ok !!!

Perché, se una classe può essere un parametro modello, uno spazio dei nomi non può.


3
GCC supporta -fno-access-control, che può essere utilizzato nei test di unità whitebox per accedere a membri della classe altrimenti privati. Questo è l'unico motivo per cui mi viene in mente di giustificare l'uso di un membro della classe anziché un globale anonimo / statico nell'implementazione.
Tom,

8
@ Tom: una soluzione multipiattaforma sarebbe quella di aggiungere il seguente codice #define private publicnelle intestazioni ... ^ _ ^ ...
paercebal

1
@Tom: comunque, IMHO, anche considerando i test unitari, i contro di "mostrare troppe cose" superano i pro. Immagino che una soluzione alternativa sarebbe quella di mettere il codice da testare in una funzione prendendo i parametri necessari (e non di più) in uno utilitiesspazio dei nomi. In questo modo, questa funzione può essere testata dall'unità e non ha ancora accesso speciale ai membri privati ​​(poiché sono indicati come parametri alla chiamata della funzione) ...
paercebal

@paercebal Sto per saltare a bordo della tua nave, ma ho un'ultima prenotazione. Se qualcuno salta nella tua namespacevolontà, non avranno accesso ai tuoi globalmembri, sebbene nascosti? Ovviamente dovrebbero indovinarlo, ma a meno che tu non stia offuscando intenzionalmente il tuo codice, i nomi delle variabili sono abbastanza facili da indovinare.
Zak,

@Zak: In effetti, potevano, ma solo provando a farlo nel file CPP in cui è dichiarata la variabile myGlobal. Il punto è più visibilità che accessibilità. Nella classe statica, la variabile myGlobal è privata, ma ancora visibile. Questo non è così importante come sembra, ma comunque, in una DLL, mostrare un simbolo che dovrebbe essere privato per la DLL in un'intestazione esportata potrebbe essere scomodo ... Nello spazio dei nomi, myGlobal esiste solo nel file CPP (tu può anche andare oltre e renderlo statico). Quella variabile non appare nelle intestazioni pubbliche.
paercebal,

63

Puoi anche creare una funzione gratuita in uno spazio dei nomi:

In BitParser.h

namespace BitParser
{
    bool getBitAt(int buffer, int bitIndex);
}

In BitParser.cpp

namespace BitParser
{
    bool getBitAt(int buffer, int bitIndex)
    {
        //get the bit :)
    }
}

In generale questo sarebbe il modo preferito di scrivere il codice. Quando non è necessario un oggetto, non utilizzare una classe.


1
In alcuni casi potresti voler avere l'incapsulamento dei dati anche se la classe è per lo più "statica". I membri statici della classe privata ti daranno questo. I membri dello spazio dei nomi sono sempre pubblici e non possono fornire l'incapsulamento dei dati.
Torleif,

Se il var "member" è dichiarato e accessibile solo dal file .cpp, è più privato di un var privato dichiarato nel file .h. NON che raccomando questa tecnica.
jmucchiello,

3
@Torleif: ti sbagli. gli spazi dei nomi sono migliori per l'incapsulamento rispetto ai membri privati ​​statici. Vedi la mia risposta per la dimostrazione.
paercebal,

1
sì, ma nello spazio dei nomi devi mantenere l'ordine delle funzioni, a differenza della classe con membri statici, ad esempio void a () {b ();} b () {} produrrebbe un errore in uno spazio dei nomi ma non in una classe con membri statici
Moataz Elmasry il

13

Se stai cercando un modo per applicare la parola chiave "statica" a una classe, come puoi ad esempio in C #

le classi statiche sono solo il compilatore che ti tiene in mano e ti impedisce di scrivere qualsiasi metodo / variabile di istanza.

Se scrivi semplicemente una classe normale senza metodi / variabili di istanza, è la stessa cosa, ed è quello che faresti in C ++


Non lamentarti (specialmente a te), ma una buona mano del compilatore per impedirmi di scrivere o tagliare / incollare la parola static200 volte sarebbe una buona cosa.
3Daveva il

D'accordo - ma neanche una classe statica in C # lo fa. Non riesce a compilare quando si dimentica di incollare statico lì :-)
Orion Edwards,

Sì, abbastanza giusto. Le mie macro stanno mostrando. Onestamente, se dichiaro la classe come statica, il compilatore dovrebbe generare un errore solo se provo ad istanziarla. Le regole che mi richiedono di ripetermi sono odiose e dovrebbero essere le prime contro il muro quando arriverà la rivoluzione.
3Dave il

11

In C ++ si desidera creare una funzione statica di una classe (non una classe statica).

class BitParser {
public:
  ...
  static ... getBitAt(...) {
  }
};

Dovresti quindi essere in grado di chiamare la funzione usando BitParser :: getBitAt () senza creare un'istanza di un oggetto che presumo sia il risultato desiderato.


11

Posso scrivere qualcosa del genere static class?

No , secondo il progetto di standard C ++ 11 N3337 allegato C 7.1.1:

Modifica: in C ++, gli identificatori statici o esterni possono essere applicati solo a nomi di oggetti o funzioni. L'uso di questi specificatori con dichiarazioni di tipo è illegale in C ++. In C, questi identificatori vengono ignorati se utilizzati su dichiarazioni di tipo. Esempio:

static struct S {    // valid C, invalid in C++
  int i;
};

Motivazione: Gli identificatori della classe di archiviazione non hanno alcun significato se associati a un tipo. In C ++, i membri della classe possono essere dichiarati con l'identificatore di classe di memoria statica. Consentire gli identificatori della classe di archiviazione sulle dichiarazioni di tipo potrebbe rendere il codice confuso per gli utenti.

E come struct, classè anche una dichiarazione di tipo.

Lo stesso può essere dedotto percorrendo l'albero della sintassi nell'Allegato A.

È interessante notare che static structera legale in C, ma non aveva alcun effetto: perché e quando usare le strutture statiche nella programmazione in C?


6

Come è stato notato qui, un modo migliore per raggiungere questo obiettivo in C ++ potrebbe essere l'utilizzo di spazi dei nomi. Ma poiché nessuno ha menzionato la finalparola chiave qui, sto postando come sarebbe un equivalente diretto di static classC # in C ++ 11 o versioni successive:

class BitParser final
{
public:
  BitParser() = delete;

  static bool GetBitAt(int buffer, int pos);
};

bool BitParser::GetBitAt(int buffer, int pos)
{
  // your code
}

5

Puoi "avere" una classe statica in C ++, come menzionato prima, una classe statica è una classe che non ne ha alcuno istante. In C ++, questo può essere ottenuto dichiarando il costruttore / distruttore come privato. Il risultato finale è lo stesso.


Quello che stai suggerendo potrebbe creare una classe singleton, ma non è la stessa di una classe statica.
ksinkar,

4

In Managed C ++, la sintassi della classe statica è: -

public ref class BitParser abstract sealed
{
    public:
        static bool GetBitAt(...)
        {
            ...
        }
}

... meglio tardi che mai...


3

Questo è simile al modo di farlo di C # in C ++

In C # file.cs puoi avere var privato all'interno di una funzione pubblica. Quando sei in un altro file puoi usarlo chiamando lo spazio dei nomi con la funzione come in:

MyNamespace.Function(blah);

Ecco come imp lo stesso in C ++:

SharedModule.h

class TheDataToBeHidden
{
  public:
    static int _var1;
    static int _var2;
};

namespace SharedData
{
  void SetError(const char *Message, const char *Title);
  void DisplayError(void);
}

SharedModule.cpp

//Init the data (Link error if not done)
int TheDataToBeHidden::_var1 = 0;
int TheDataToBeHidden::_var2 = 0;


//Implement the namespace
namespace SharedData
{
  void SetError(const char *Message, const char *Title)
  {
    //blah using TheDataToBeHidden::_var1, etc
  }

  void DisplayError(void)
  {
    //blah
  }
}

OtherFile.h

#include "SharedModule.h"

OtherFile.cpp

//Call the functions using the hidden variables
SharedData::SetError("Hello", "World");
SharedData::DisplayError();

2
Ma tutti possono rinunciare a TheDataToBeHidden -> Non è una soluzione
Guy L

3

A differenza di altri linguaggi di programmazione gestiti, "classe statica" NON ha significato in C ++. È possibile utilizzare la funzione membro statico.


0

Un caso in cui gli spazi dei nomi potrebbero non essere così utili per ottenere "classi statiche" è quando si usano queste classi per ottenere composizione sull'ereditarietà. Gli spazi dei nomi non possono essere amici delle classi e quindi non possono accedere ai membri privati ​​di una classe.

class Class {
 public:
  void foo() { Static::bar(*this); }    

 private:
  int member{0};
  friend class Static;
};    

class Static {
 public:
  template <typename T>
  static void bar(T& t) {
    t.member = 1;
  }
};

0

Una (delle tante) alternative, ma la più (secondo la mia opinione) elegante (rispetto all'utilizzo di spazi dei nomi e costruttori privati ​​per emulare il comportamento statico), il modo per ottenere il comportamento di "classe che non può essere istanziato" in C ++ sarebbe quello di dichiarare una funzione virtuale pura fittizia con il privatemodificatore di accesso.

class Foo {
   public:
     static int someMethod(int someArg);

   private:
     virtual void __dummy() = 0;
};

Se si utilizza C ++ 11, è possibile fare il possibile per assicurarsi che la classe non sia ereditata (per emulare puramente il comportamento di una classe statica) utilizzando l' finalidentificatore nella dichiarazione di classe per impedire alle altre classi di ereditarla .

// C++11 ONLY
class Foo final {
   public:
     static int someMethod(int someArg);

   private:
      virtual void __dummy() = 0;
};

Per quanto sembri sciocco e illogico, C ++ 11 consente la dichiarazione di "pura funzione virtuale che non può essere ignorata", che puoi usare insieme a dichiarare alla classe finaldi implementare in modo puro e completo il comportamento statico poiché ciò si traduce nel risultato classe per non essere ereditabile e la funzione fittizia per non essere ignorata in alcun modo.

// C++11 ONLY
class Foo final {
   public:
     static int someMethod(int someArg);

   private:
     // Other private declarations

     virtual void __dummy() = 0 final;
}; // Foo now exhibits all the properties of a static class
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.