Comment créez-vous une classe statique en C ++?

264

Comment créez-vous une classe statique en C ++? Je devrais être capable de faire quelque chose comme:

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

En supposant que j'ai créé la BitParserclasse. À quoi BitParserressemblerait la définition de classe?

andrewrk
la source
198
Oui. Dans le passé, nous appelions simplement cela une "fonction". Vous les enfants aujourd'hui avec vos "espaces de noms" fous. Hé, descendez de ma pelouse! <secoue le poing>
Vagrant
7
@Vagrant une fonction à l'intérieur d'un espace de noms est toujours une fonction. Une fonction qui appartient à une classe est appelée une méthode. S'il s'agit d'une méthode statique, vous l'invoquez de la même manière que s'il s'agissait d'une fonction à l'intérieur d'un espace de noms.
48
5 ans plus tard, je suis maintenant du côté de @Vagrant. Quelle question idiote!
andrewrk
16
9 ans plus tard et maintenant j'ai mon propre langage de programmation qui n'a même pas de cours: ziglang.org
andrewrk
1
Les classes de type conteneur IMO (ayant uniquement des méthodes statiques) sont utiles dans certains cas.
AarCee

Réponses:

272

Si vous cherchez un moyen d'appliquer le mot clé "statique" à une classe, comme vous pouvez le faire en C # par exemple, vous ne pourrez pas le faire sans utiliser Managed C ++.

Mais l'apparence de votre exemple, il vous suffit de créer une méthode statique publique sur votre objet BitParser. Ainsi:

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

Vous pouvez utiliser ce code pour appeler la méthode de la même manière que votre exemple de code.

J'espère que cela pourra aider! À votre santé.

JO.
la source
2
JO, vous avez une erreur de syntaxe . Le mot clé statique ne doit être utilisé que dans la définition de classe et non dans la définition de méthode.
andrewrk
90
Pour clarifier votre intention dans cette approche, vous pouvez également utiliser un constructeur privé. private: BitParser() {}Cela empêchera quiconque de créer des instances.
Danvil
7
La sécurité des threads @MoatazElmasry est un problème lorsque vous partagez l'état. Dans l'implémentation ci-dessus, il n'y a pas d'état partagé, donc il ne peut pas y avoir de problème avec la sécurité des threads ... à moins que vous ne soyez assez stupide pour utiliser la statique dans ces fonctions. Alors oui, le code ci-dessus est thread-safe, gardez simplement l'état persistant hors de vos fonctions et vous êtes bon.
JO.
@MoatazElmasry Incorrect. Deux threads ne peuvent pas modifier les variables locales non statiques dans une fonction statique.
JO.
12
Si C ++ 11, je dirais qu'il est préférable de BitParser() = delete;transmettre correctement l'intention de supprimer le constructeur (et pas seulement de le cacher private).
phoenix
247

Considérez la solution de Matt Price .

  1. En C ++, une "classe statique" n'a pas de sens. La chose la plus proche est une classe avec uniquement des méthodes et des membres statiques.
  2. L'utilisation de méthodes statiques ne fera que vous limiter.

Ce que vous voulez est exprimé dans la sémantique C, pour mettre votre fonction (car il est une fonction) dans un espace de noms.

Modifier 2011-11-11

Il n'y a pas de "classe statique" en C ++. Le concept le plus proche serait une classe avec uniquement des méthodes statiques. Par exemple:

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

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

Mais vous devez vous rappeler que les "classes statiques" sont des hacks dans les langages de type Java (par exemple C #) qui ne peuvent pas avoir de fonctions non membres, donc ils doivent plutôt les déplacer à l'intérieur des classes en tant que méthodes statiques.

En C ++, ce que vous voulez vraiment, c'est une fonction non membre que vous déclarerez dans un espace de noms:

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

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

Pourquoi donc?

En C ++, l'espace de noms est plus puissant que les classes pour le modèle "Java static method", car:

  • les méthodes statiques ont accès aux symboles privés des classes
  • les méthodes statiques privées sont toujours visibles (si inaccessibles) pour tout le monde, ce qui viole quelque peu l'encapsulation
  • les méthodes statiques ne peuvent pas être déclarées en aval
  • les méthodes statiques ne peuvent pas être surchargées par l'utilisateur de la classe sans modifier l'en-tête de la bibliothèque
  • rien ne peut être fait par une méthode statique qui ne peut pas être fait mieux qu'une fonction non-membre (peut-être un ami) dans le même espace de noms
  • les espaces de noms ont leur propre sémantique (ils peuvent être combinés, ils peuvent être anonymes, etc.)
  • etc.

Conclusion: ne copiez / collez pas le modèle Java / C # en C ++. En Java / C #, le modèle est obligatoire. Mais en C ++, c'est du mauvais style.

Modifier le 2010-06-10

Il y avait un argument en faveur de la méthode statique, car parfois, il faut utiliser une variable de membre privé statique.

Je suis en désaccord quelque peu, comme indiqué ci-dessous:

La solution "Membre Privé Statique"

// HPP

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

Tout d'abord, myGlobal est appelé myGlobal car il s'agit toujours d'une variable privée globale. Un regard sur la source du RPC clarifiera que:

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

À première vue, le fait que la fonction gratuite barC ne puisse pas accéder à Foo :: myGlobal semble une bonne chose du point de vue de l'encapsulation ... C'est cool parce que quelqu'un qui regarde le HPP ne pourra pas (sauf s'il a recours au sabotage) accéder Foo :: myGlobal.

Mais si vous y regardez de près, vous constaterez que c'est une erreur colossale: non seulement votre variable privée doit toujours être déclarée dans le HPP (et donc, visible par le monde entier, bien qu'elle soit privée), mais vous devez déclarer dans le même HPP toutes les fonctions (comme dans TOUS) qui seront autorisées à y accéder !!!

Donc, utiliser un membre statique privé, c'est comme marcher dehors nu avec la liste de vos amants tatoués sur votre peau: Personne n'est autorisé à toucher, mais tout le monde peut y jeter un œil. Et le bonus: tout le monde peut avoir les noms des personnes autorisées à jouer avec vos amis.

private en effet ... :-D

La solution "espaces de noms anonymes"

Les espaces de noms anonymes auront l'avantage de rendre les choses privées vraiment privées.

Tout d'abord, l'en-tête HPP

// HPP

namespace Foo
{
   void barA() ;
}

Juste pour être sûr que vous avez remarqué: il n'y a pas de déclaration inutile de barB ni de myGlobal. Ce qui signifie que personne ne lisant l'en-tête ne sait ce qui se cache derrière barA.

Ensuite, le RPC:

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

Comme vous pouvez le voir, comme la déclaration dite de "classe statique", fooA et fooB peuvent toujours accéder à myGlobal. Mais personne d'autre ne le peut. Et personne d'autre en dehors de ce CPP ne sait que fooB et myGlobal existent même!

Contrairement à la "classe statique" marchant sur le nu avec son carnet d'adresses tatoué sur la peau, l'espace de noms "anonyme" est entièrement habillé , ce qui semble bien mieux encapsulé AFAIK.

Est-ce que c'est vraiment important?

À moins que les utilisateurs de votre code ne soient des saboteurs (je vous laisse, comme exercice, trouver comment on peut accéder à la partie privée d'une classe publique en utilisant un hack sale non défini ...), qu'est-ce qui privatese passe private, même s'il est visible dans la privatesection d'une classe déclarée dans un en-tête.

Pourtant, si vous avez besoin d'ajouter une autre "fonction privée" avec accès au membre privé, vous devez toujours la déclarer à tout le monde en modifiant l'en-tête, ce qui est un paradoxe en ce qui me concerne: si je change la mise en œuvre de mon code (la partie CPP), puis l'interface (la partie HPP) ne doit PAS changer. Citant Leonidas: " C'est de l'ENCAPSULATION! "

Modifier le 2014-09-20

Quand les classes les méthodes statiques sont-elles réellement meilleures que les espaces de noms avec des fonctions non membres?

Lorsque vous devez regrouper des fonctions et alimenter ce groupe dans un modèle:

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

Parce que, si une classe peut être un paramètre de modèle, les espaces de noms ne le peuvent pas.

paercebal
la source
3
GCC prend en charge -fno-access-control, qui peut être utilisé dans des tests unitaires en boîte blanche pour accéder à des membres de classe autrement privés. C'est à peu près la seule raison à laquelle je peux penser pour justifier l'utilisation d'un membre de classe au lieu d'un global anonyme / statique dans l'implémentation.
Tom
8
@Tom: Une solution multiplateforme serait d'ajouter le code suivant #define private publicdans les en-têtes ... ^ _ ^ ...
paercebal
1
@Tom: de toute façon, à mon humble avis, même en considérant les tests unitaires, les inconvénients de "montrer trop de choses" l'emportent sur les pros. Je suppose qu'une solution alternative serait de mettre le code à tester dans une fonction prenant les paramètres nécessaires (et pas plus) dans un utilitiesespace de noms. De cette façon, cette fonction peut être testée à l'unité, et n'a toujours pas d'accès spécial aux membres privés (car ils sont donnés comme paramètres lors de l'appel de fonction) ...
paercebal
@paercebal Je suis sur le point de monter à bord de votre navire, mais j'ai une dernière réservation. Si quelqu'un saute dans votre namespacevolonté, n'aura-t-il pas accès à vos membres global, bien que cachés? De toute évidence, ils devraient deviner, mais à moins que vous ne masquiez intentionnellement votre code, les noms de variables sont assez faciles à deviner.
Zak
@Zak: En effet, ils pourraient, mais seulement en essayant de le faire dans le fichier CPP où la variable myGlobal est déclarée. Le point est plus de visibilité que d'accessibilité. Dans la classe statique, la variable myGlobal est privée, mais toujours visible. Ce n'est pas aussi important qu'il y paraît, mais quand même, dans une DLL, afficher un symbole qui devrait être privé de la DLL dans un en-tête exporté pourrait être gênant ... Dans l'espace de noms, le myGlobal n'existe que dans le fichier CPP (vous peut même aller plus loin et le rendre statique). Cette variable n'apparaît pas dans les en-têtes publics.
paercebal
63

Vous pouvez également créer une fonction libre dans un espace de noms:

Dans BitParser.h

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

Dans BitParser.cpp

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

En général, ce serait la manière préférée d'écrire le code. Lorsqu'il n'y a pas besoin d'un objet, n'utilisez pas de classe.

Prix ​​Matt
la source
1
Dans certains cas, vous souhaiterez peut-être l'encapsulation des données même si la classe est principalement "statique". Les membres de la classe privée statique vous le donneront. Les membres de l'espace de noms sont toujours publics et ne peuvent pas fournir l'encapsulation des données.
Torleif
Si la variable "membre" est uniquement déclarée et accessible à partir du fichier .cpp, elle est plus privée qu'une variable privée déclarée dans le fichier .h. PAS que je recommande cette technique.
jmucchiello
3
@Torleif: Vous vous trompez. les espaces de noms sont meilleurs pour l'encapsulation que les membres privés statiques. Voir ma réponse pour démonstration.
paercebal
1
oui, mais dans l'espace de noms, vous devez conserver l'ordre des fonctions, contrairement à la classe avec des membres statiques, par exemple void a () {b ();} b () {} donnerait une erreur dans un espace de noms mais pas dans une classe avec membres statiques
Moataz Elmasry
13

Si vous cherchez un moyen d'appliquer le mot clé "statique" à une classe, comme vous pouvez le faire en C # par exemple

Les classes statiques sont simplement le compilateur qui vous tient à la main et vous empêche d'écrire des méthodes / variables d'instance.

Si vous écrivez simplement une classe normale sans aucune méthode / variable d'instance, c'est la même chose, et c'est ce que vous feriez en C ++

Orion Edwards
la source
Ne pas me plaindre (surtout chez vous), mais une tenue de main du compilateur pour m'empêcher d'écrire ou de couper / coller le mot static200 fois serait une bonne chose.
3Dave
D'accord - mais une classe statique en C # ne fait pas cela non plus. Il ne parvient tout simplement pas à se compiler lorsque vous oubliez d'y coller de l'électricité statique :-)
Orion Edwards
Ouais - assez bien. Mes macros s'affichent. Honnêtement, si je déclare la classe comme statique, le compilateur ne devrait lancer une erreur que si j'essaye de l'instancier. Les règles qui m'obligent à me répéter sont odieuses et devraient être les premières contre le mur lorsque la révolution viendra.
3Dave
11

En C ++, vous souhaitez créer une fonction statique d'une classe (pas une classe statique).

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

Vous devriez alors être en mesure d'appeler la fonction en utilisant BitParser :: getBitAt () sans instancier un objet que je suppose être le résultat souhaité.

Philip Reynolds
la source
11

Puis-je écrire quelque chose comme static class?

Non , selon le projet d' annexe C 7.1.1 de la norme C ++ 11 N3337 :

Modification: en C ++, les spécificateurs statiques ou externes ne peuvent être appliqués qu'aux noms d'objets ou de fonctions. L'utilisation de ces spécificateurs avec des déclarations de type est illégale en C ++. En C, ces spécificateurs sont ignorés lorsqu'ils sont utilisés sur les déclarations de type. Exemple:

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

Justification: Les spécificateurs de classe de stockage n'ont aucune signification lorsqu'ils sont associés à un type. En C ++, les membres de classe peuvent être déclarés avec le spécificateur de classe de stockage statique. Autoriser les spécificateurs de classe de stockage sur les déclarations de type peut rendre le code déroutant pour les utilisateurs.

Et comme struct, classc'est aussi une déclaration de type.

La même chose peut être déduite en parcourant l'arbre de syntaxe de l'annexe A.

Il est intéressant de noter que static structc'était légal en C, mais n'a eu aucun effet: pourquoi et quand utiliser des structures statiques en programmation C?

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

Comme il a été noté ici, un meilleur moyen d'y parvenir en C ++ pourrait être d'utiliser des espaces de noms. Mais puisque personne n'a mentionné le finalmot - clé ici, je poste à quoi ressemblerait un équivalent direct static classde C # en C ++ 11 ou version ultérieure:

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

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

bool BitParser::GetBitAt(int buffer, int pos)
{
  // your code
}
Mikhail Vasilyev
la source
5

Vous 'pouvez' avoir une classe statique en C ++, comme mentionné précédemment, une classe statique en est une qui n'en a aucun objet instanciée. En C ++, cela peut être obtenu en déclarant le constructeur / destructeur privé. Le résultat final est le même.

Netzer
la source
Ce que vous proposez pourrait créer une classe singleton, mais ce n'est pas la même chose qu'une classe statique.
ksinkar
4

En C ++ managé, la syntaxe de classe statique est: -

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

... Mieux vaut tard que jamais...

Malc B
la source
3

Ceci est similaire à la façon de faire de C # en C ++

Dans C # file.cs, vous pouvez avoir une var privée dans une fonction publique. Dans un autre fichier, vous pouvez l'utiliser en appelant l'espace de noms avec la fonction comme dans:

MyNamespace.Function(blah);

Voici comment faire la même chose en 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();
John
la source
2
Mais tout le monde peut accéder à TheDataToBeHidden -> Ce n'est pas une solution
Guy L
3

Contrairement à d'autres langages de programmation managés, "classe statique" n'a AUCUNE signification en C ++. Vous pouvez utiliser la fonction membre statique.

Bharath Ravindra
la source
0

Un cas où les espaces de noms peuvent ne pas être aussi utiles pour atteindre des "classes statiques" est lors de l'utilisation de ces classes pour obtenir une composition sur l'héritage. Les espaces de noms ne peuvent pas être amis des classes et ne peuvent donc pas accéder aux membres privés d'une 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;
  }
};
Josh Olson
la source
0

Une (parmi les nombreuses) alternatives, mais la plus (à mon avis) élégante (par rapport à l'utilisation d'espaces de noms et de constructeurs privés pour émuler le comportement statique), la manière d'obtenir le comportement "classe qui ne peut pas être instanciée" en C ++ serait de déclarer une fonction virtuelle pure factice avec le privatemodificateur d'accès.

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

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

Si vous utilisez C ++ 11, vous pouvez aller plus loin pour vous assurer que la classe n'est pas héritée (pour purement émuler le comportement d'une classe statique) en utilisant le finalspécificateur dans la déclaration de classe pour empêcher les autres classes de l'hériter. .

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

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

Aussi stupide et illogique que cela puisse paraître, C ++ 11 permet la déclaration d'une "fonction virtuelle pure qui ne peut pas être remplacée", que vous pouvez utiliser en même temps que déclarer la classe finalpour implémenter purement et entièrement le comportement statique car cela entraîne la résultante classe ne doit pas être héritable et la fonction factice ne doit pas être remplacée en aucune façon.

// 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
la source