Wie erstellt man eine statische Klasse in C ++?

262

Wie erstellt man eine statische Klasse in C ++? Ich sollte in der Lage sein, etwas zu tun wie:

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

Angenommen, ich habe die BitParserKlasse erstellt. Wie würde die BitParserKlassendefinition aussehen?

andrewrk
quelle
198
Ja. Früher haben wir das nur eine "Funktion" genannt. Sie Kinder heute mit Ihren verrückten "Namespaces". Hey, geh von meinem Rasen! <schüttelt die Faust>
Vagrant
7
@Vagrant Eine Funktion in einem Namespace ist immer noch eine Funktion. Eine Funktion, die zu einer Klasse gehört, wird als Methode bezeichnet. Wenn es sich um eine statische Methode handelt, rufen Sie sie ähnlich auf, als wäre sie eine Funktion in einem Namespace.
48
5 Jahre später und jetzt bin ich auf der Seite von @Vagrant. Was für eine dumme Frage!
Andrewrk
16
9 Jahre später und jetzt habe ich meine eigene Programmiersprache, die nicht einmal Klassen hat: ziglang.org
andrewrk
1
IMO-Container-ähnliche Klassen (mit nur statischen Methoden) sind in bestimmten Fällen nützlich.
AarCee

Antworten:

271

Wenn Sie nach einer Möglichkeit suchen, das Schlüsselwort "static" auf eine Klasse anzuwenden, wie dies beispielsweise in C # möglich ist, können Sie dies nicht ohne die Verwendung von Managed C ++ tun.

Für das Aussehen Ihres Beispiels müssen Sie lediglich eine öffentliche statische Methode für Ihr BitParser-Objekt erstellen. Wie so:

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

Mit diesem Code können Sie die Methode auf dieselbe Weise wie Ihren Beispielcode aufrufen.

Ich hoffe, das hilft! Prost.

ABl.
quelle
2
ABl. Sie haben einen Syntaxfehler . Das statische Schlüsselwort sollte nur in der Klassendefinition und nicht in der Methodendefinition verwendet werden.
Andrewrk
90
Um Ihre Absicht in diesem Ansatz klar zu machen, können Sie zusätzlich einen privaten Konstruktor verwenden. private: BitParser() {}Dadurch wird verhindert, dass jemand Instanzen erstellt.
Danvil
7
Die Sicherheit von @ MoatazElmasry-Threads ist ein Problem, wenn Sie den Status freigeben. In der obigen Implementierung gibt es keinen gemeinsam genutzten Status, daher kann es keine Probleme mit der Thread-Sicherheit geben ... es sei denn, Sie sind dumm genug, Statik in diesen Funktionen zu verwenden. Also ja, der obige Code ist threadsicher. Halten Sie einfach den dauerhaften Status von Ihren Funktionen fern und Sie sind gut.
ABl.
@ MoatazElmasry Falsch. Zwei Threads können nicht statische lokale Variablen in einer statischen Funktion nicht ändern.
ABl.
12
Wenn C ++ 11, würde ich argumentieren, dass es besser BitParser() = delete;ist, die Absicht, den Konstruktor zu entfernen, richtig zu vermitteln (und ihn nicht nur als zu verbergen private).
Phoenix
247

Betrachten Sie die Lösung von Matt Price .

  1. In C ++ hat eine "statische Klasse" keine Bedeutung. Das nächste ist eine Klasse mit nur statischen Methoden und Mitgliedern.
  2. Die Verwendung statischer Methoden schränkt Sie nur ein.

Was Sie wollen, ist, ausgedrückt in C ++ - Semantik, Ihre Funktion (denn es ist eine Funktion) in einen Namespace zu setzen.

Bearbeiten 2011-11-11

In C ++ gibt es keine "statische Klasse". Das nächste Konzept wäre eine Klasse mit nur statischen Methoden. Beispielsweise:

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

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

Sie müssen sich jedoch daran erinnern, dass "statische Klassen" Hacks in Java-ähnlichen Sprachen (z. B. C #) sind, die keine Nichtmitgliedsfunktionen haben können. Sie müssen sie stattdessen als statische Methoden in Klassen verschieben.

In C ++ möchten Sie wirklich eine Nichtmitgliedsfunktion, die Sie in einem Namespace deklarieren:

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

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

Warum ist das so?

In C ++ ist der Namespace für das Muster "Statische Java-Methode" leistungsfähiger als Klassen, weil:

  • statische Methoden haben Zugriff auf die privaten Symbole der Klassen
  • Private statische Methoden sind weiterhin für alle sichtbar (wenn nicht zugänglich), was die Kapselung etwas verletzt
  • statische Methoden können nicht vorwärts deklariert werden
  • statische Methoden können vom Klassenbenutzer nicht überladen werden, ohne den Bibliotheksheader zu ändern
  • Es gibt nichts, was mit einer statischen Methode ausgeführt werden kann, die nicht besser ausgeführt werden kann als eine (möglicherweise befreundete) Nichtmitgliedsfunktion im selben Namespace
  • Namespaces haben ihre eigene Semantik (sie können kombiniert werden, sie können anonym sein usw.)
  • etc.

Schlussfolgerung: Kopieren Sie das Java / C # -Muster nicht in C ++. In Java / C # ist das Muster obligatorisch. Aber in C ++ ist es ein schlechter Stil.

Bearbeiten 2010-06-10

Es gab ein Argument für die statische Methode, da manchmal eine statische private Mitgliedsvariable verwendet werden muss.

Ich bin etwas anderer Meinung, wie unten gezeigt:

Die "Static Private Member" -Lösung

// HPP

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

Erstens heißt myGlobal myGlobal, da es immer noch eine globale private Variable ist. Ein Blick auf die CPP-Quelle verdeutlicht Folgendes:

// 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 !!!
}

Auf den ersten Blick scheint die Tatsache, dass die freie Funktion barC nicht auf Foo :: myGlobal zugreifen kann, aus Sicht der Kapselung eine gute Sache zu sein ... Es ist cool, weil jemand, der sich das HPP ansieht, nicht darauf zugreifen kann (es sei denn, er greift auf Sabotage zurück) Foo :: myGlobal.

Wenn Sie es sich jedoch genau ansehen, werden Sie feststellen, dass es sich um einen kolossalen Fehler handelt: Nicht nur Ihre private Variable muss im HPP deklariert sein (und somit für die ganze Welt sichtbar sein, obwohl sie privat ist), sondern Sie müssen sie auch deklarieren im selben HPP alle (wie in ALLEN) Funktionen, die berechtigt sind, darauf zuzugreifen !!!

Wenn Sie also ein privates statisches Mitglied verwenden, gehen Sie nackt mit der Liste Ihrer Liebenden, die auf Ihre Haut tätowiert sind, nach draußen: Niemand darf berühren, aber jeder kann einen Blick darauf werfen. Und der Bonus: Jeder kann die Namen derjenigen haben, die berechtigt sind, mit Ihren Geheimnissen zu spielen.

private in der Tat ... :-D

Die Lösung "Anonyme Namespaces"

Anonyme Namespaces haben den Vorteil, dass sie Dinge wirklich privat machen.

Zunächst der HPP-Header

// HPP

namespace Foo
{
   void barA() ;
}

Nur um sicherzugehen, dass Sie bemerkt haben: Es gibt keine nutzlose Erklärung von barB oder myGlobal. Das bedeutet, dass niemand, der den Header liest, weiß, was sich hinter barA verbirgt.

Dann ist der 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 !!!
}

Wie Sie sehen, können fooA und fooB wie die sogenannte "static class" -Deklaration weiterhin auf myGlobal zugreifen. Aber sonst kann niemand. Und niemand außerhalb dieses CPP weiß, dass fooB und myGlobal überhaupt existieren!

Im Gegensatz zu der "statischen Klasse", die nackt mit ihrem auf die Haut tätowierten Adressbuch läuft , ist der "anonyme" Namespace vollständig bekleidet , was AFAIK besser zu verkapseln scheint.

Ist es wirklich wichtig?

Es sei denn, die Benutzer Ihres Codes sind Saboteure (ich lasse Sie als Übung herausfinden, wie man mit einem schmutzigen, nicht definierten Hack auf den privaten Teil einer öffentlichen Klasse zugreifen kann ...), was privateist private, auch wenn es so ist ist im privateAbschnitt einer Klasse sichtbar, die in einem Header deklariert ist.

Wenn Sie jedoch eine weitere "private Funktion" mit Zugriff auf das private Mitglied hinzufügen müssen, müssen Sie sie dennoch der ganzen Welt deklarieren, indem Sie den Header ändern. Dies ist für mich ein Paradoxon: Wenn ich die Implementierung von ändere Mein Code (der CPP-Teil), dann sollte sich die Schnittstelle (der HPP-Teil) NICHT ändern. Zitat von Leonidas: " Das ist ENCAPSULATION! "

Bearbeiten 20.09.2014

Wann sind statische Klassenmethoden tatsächlich besser als Namespaces mit Nicht-Member-Funktionen?

Wenn Sie Funktionen gruppieren und diese Gruppe einer Vorlage zuordnen müssen:

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 !!!

Denn wenn eine Klasse ein Vorlagenparameter sein kann, können Namespaces dies nicht.

paercebal
quelle
3
GCC unterstützt -fno-access-control, das in Whitebox-Unit-Tests verwendet werden kann, um auf ansonsten private Klassenmitglieder zuzugreifen. Dies ist ungefähr der einzige Grund, warum ich mir vorstellen kann, die Verwendung eines Klassenmitglieds anstelle eines anonymen / statischen Globals in der Implementierung zu rechtfertigen.
Tom
8
@ Tom: Eine plattformübergreifende Lösung wäre, den folgenden Code #define private publicin die Header einzufügen ... ^ _ ^ ...
paercebal
1
@ Tom: Wie auch immer, IMHO, selbst wenn man Unit-Tests in Betracht zieht, überwiegen die Nachteile, "zu viel Zeug zu zeigen", die Vorteile. Ich denke, eine alternative Lösung wäre, den zu testenden Code in eine Funktion zu setzen, die die benötigten Parameter (und nicht mehr) in einem utilitiesNamespace verwendet. Auf diese Weise kann diese Funktion einem Unit-Test
unterzogen werden
@paercebal Ich bin im Begriff, an Bord Ihres Schiffes zu springen, aber ich habe eine letzte Reservierung. Wenn jemand in Ihren namespaceWillen springt, erhält er keinen Zugang zu Ihren global, wenn auch versteckten Mitgliedern? Natürlich müssten sie raten, aber wenn Sie Ihren Code nicht absichtlich verschleiern, sind Variablennamen ziemlich einfach zu erraten.
Zak
@Zak: In der Tat könnten sie, aber nur, indem sie versuchen, dies in der CPP-Datei zu tun, in der die Variable myGlobal deklariert ist. Der Punkt ist mehr Sichtbarkeit als Zugänglichkeit. In der statischen Klasse ist die Variable myGlobal privat, aber immer noch sichtbar. Dies ist nicht so wichtig, wie es scheint, aber dennoch kann es in einer DLL umständlich sein, ein Symbol anzuzeigen, das für die DLL in einem exportierten Header privat sein sollte ... Im Namespace ist myGlobal nur in der CPP-Datei vorhanden (Sie kann sogar noch weiter gehen und es statisch machen). Diese Variable wird nicht in den öffentlichen Headern angezeigt.
Paercebal
63

Sie können auch eine kostenlose Funktion in einem Namespace erstellen:

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 :)
    }
}

Im Allgemeinen wäre dies die bevorzugte Methode zum Schreiben des Codes. Wenn kein Objekt benötigt wird, verwenden Sie keine Klasse.

Matt Price
quelle
1
In einigen Fällen möchten Sie möglicherweise eine Datenkapselung, auch wenn die Klasse größtenteils "statisch" ist. Statische private Klassenmitglieder geben Ihnen dies. Namespace-Mitglieder sind immer öffentlich und können keine Datenkapselung bereitstellen.
Torleif
Wenn die Variable "member" nur aus der CPP-Datei deklariert wird und auf sie zugegriffen wird, ist sie privater als eine private Variable, die in der H-Datei deklariert ist. NICHT, dass ich diese Technik empfehle.
jmucchiello
3
@ Torleif: Du liegst falsch. Namespaces eignen sich besser für die Kapselung als statische private Mitglieder. Siehe meine Antwort zur Demonstration.
Paercebal
1
Ja, aber im Namespace müssen Sie die Funktionsreihenfolge beibehalten, im Gegensatz zu Klassen mit statischen Elementen. Beispielsweise würde void a () {b ();} b () {} einen Fehler in einem Namespace ergeben, jedoch nicht in einer Klasse mit statische Mitglieder
Moataz Elmasry
13

Wenn Sie nach einer Möglichkeit suchen, das Schlüsselwort "static" auf eine Klasse anzuwenden, wie Sie dies beispielsweise in C # tun können

statische Klassen sind nur der Compiler, der Sie in der Hand hält und Sie daran hindert, Instanzmethoden / -variablen zu schreiben.

Wenn Sie nur eine normale Klasse ohne Instanzmethoden / -variablen schreiben, ist dies dasselbe, und dies würden Sie in C ++ tun

Orion Edwards
quelle
Sich nicht zu beschweren (besonders bei Ihnen), aber ein bisschen Hand in der Hand des Compilers, um mich davon abzuhalten, das Wort static200 Mal zu schreiben oder auszuschneiden / einzufügen, wäre eine gute Sache.
3Dave
Einverstanden - aber eine statische Klasse in C # macht das auch nicht. Es kann einfach nicht kompiliert werden, wenn Sie vergessen, statische Daten dort einzufügen :-)
Orion Edwards
Ja - fair genug. Meine Makros werden angezeigt. Ehrlich gesagt, wenn ich die Klasse als statisch deklariere, sollte der Compiler nur dann einen Fehler auslösen, wenn ich versuche, ihn zu instanziieren. Regeln, nach denen ich mich wiederholen muss, sind widerlich und sollten die ersten an der Wand sein, wenn die Revolution kommt.
3Dave
11

In C ++ möchten Sie eine statische Funktion einer Klasse erstellen (keine statische Klasse).

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

Sie sollten dann in der Lage sein, die Funktion mit BitParser :: getBitAt () aufzurufen, ohne ein Objekt zu instanziieren, von dem ich annehme, dass es das gewünschte Ergebnis ist.

Philip Reynolds
quelle
11

Kann ich so etwas schreiben static class?

Nein , gemäß C ++ 11 N3337 Standardentwurf Anhang C 7.1.1:

Änderung: In C ++ können die statischen oder externen Bezeichner nur auf Namen von Objekten oder Funktionen angewendet werden. Die Verwendung dieser Bezeichner mit Typdeklarationen ist in C ++ unzulässig. In C werden diese Bezeichner ignoriert, wenn sie für Typdeklarationen verwendet werden. Beispiel:

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

Begründung: Speicherklassenspezifizierer haben keine Bedeutung, wenn sie einem Typ zugeordnet sind. In C ++ können Klassenmitglieder mit dem statischen Speicherklassenspezifizierer deklariert werden. Das Zulassen von Speicherklassenspezifizierern für Typdeklarationen kann den Code für Benutzer verwirrend machen.

Und wie struct, classist auch eine Art Erklärung.

Dasselbe kann durch Durchlaufen des Syntaxbaums in Anhang A abgeleitet werden.

Es ist interessant festzustellen, dass dies static structin C legal war, aber keine Auswirkungen hatte: Warum und wann sollten statische Strukturen in der C-Programmierung verwendet werden?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
6

Wie hier angemerkt, könnte ein besserer Weg, dies in C ++ zu erreichen, die Verwendung von Namespaces sein. Da hier jedoch niemand das finalSchlüsselwort erwähnt hat, poste ich, wie ein direktes Äquivalent static classvon C # in C ++ 11 oder höher aussehen würde:

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

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

bool BitParser::GetBitAt(int buffer, int pos)
{
  // your code
}
Michail Wassiljew
quelle
5

Sie können eine statische Klasse in C ++ haben, wie bereits erwähnt. Eine statische Klasse ist eine Klasse, für die keine Objekte instanziiert wurden. In C ++ kann dies erreicht werden, indem der Konstruktor / Destruktor als privat deklariert wird. Das Endergebnis ist das gleiche.

Netzer
quelle
Was Sie vorschlagen, erstellt möglicherweise eine Singleton-Klasse, ist jedoch nicht mit einer statischen Klasse identisch.
Ksinkar
4

In Managed C ++ lautet die statische Klassensyntax: -

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

... besser spät als nie...

Malc B.
quelle
3

Dies ähnelt der Vorgehensweise von C # in C ++

In C # file.cs können Sie private var in einer öffentlichen Funktion haben. In einer anderen Datei können Sie sie verwenden, indem Sie den Namespace mit der folgenden Funktion aufrufen:

MyNamespace.Function(blah);

So können Sie dasselbe in C ++ tun:

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();
John
quelle
2
Aber jeder kommt zu TheDataToBeHidden -> Es ist keine Lösung
Guy L
3

Im Gegensatz zu anderen verwalteten Programmiersprachen hat "statische Klasse" in C ++ KEINE Bedeutung. Sie können die statische Elementfunktion verwenden.

Bharath Ravindra
quelle
0

Ein Fall, in dem Namespaces möglicherweise nicht so nützlich sind, um "statische Klassen" zu erreichen, ist die Verwendung dieser Klassen, um eine Komposition über Vererbung zu erreichen. Namespaces können keine Freunde von Klassen sein und können daher nicht auf private Mitglieder einer Klasse zugreifen.

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;
  }
};
Josh Olson
quelle
0

Eine (der vielen) Alternativen, aber die (meiner Meinung nach) eleganteste (im Vergleich zur Verwendung von Namespaces und privaten Konstruktoren zur Emulation des statischen Verhaltens), um das Verhalten "Klasse, die nicht instanziiert werden kann" in C ++ zu erreichen, wäre: Deklarieren Sie eine reine virtuelle Dummy-Funktion mit dem privateZugriffsmodifikator.

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

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

Wenn Sie C ++ 11 verwenden, können Sie zusätzliche Anstrengungen unternehmen, um sicherzustellen, dass die Klasse nicht vererbt wird (um das Verhalten einer statischen Klasse nur zu emulieren), indem Sie den finalBezeichner in der Klassendeklaration verwenden, um die anderen Klassen daran zu hindern, sie zu erben .

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

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

So albern und unlogisch es auch klingen mag, C ++ 11 ermöglicht die Deklaration einer "reinen virtuellen Funktion, die nicht überschrieben werden kann", die Sie neben der Deklaration der Klasse verwenden können final, um das statische Verhalten rein und vollständig zu implementieren, da dies zu dem Ergebnis führt Klasse darf nicht vererbbar sein und die Dummy-Funktion darf in keiner Weise überschrieben werden.

// 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
hecate
quelle