Jak stworzyć klasę statyczną w C ++?


263

Jak stworzyć klasę statyczną w C ++? Powinienem być w stanie zrobić coś takiego:

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

Zakładając, że stworzyłem BitParserklasę. Jak wyglądałaby BitParserdefinicja klasy?


198
Tak. W dawnych czasach nazywaliśmy to „funkcją”. Dzieciaki dzisiaj z waszymi szalonymi „przestrzeniami nazw”. Hej, zejdź z mojego trawnika! <potrząsa pięścią
włóczęga

7
@ Vagrant funkcja w przestrzeni nazw jest nadal funkcją. Funkcja należąca do klasy nazywana jest metodą. Jeśli jest to metoda statyczna, wywołujesz ją podobnie, jakby była funkcją wewnątrz przestrzeni nazw.

48
5 lat później i teraz jestem po stronie @Vagrant. Co za głupie pytanie!
andrewrk

16
9 lat później, a teraz mam swój własny język programowania, który nawet nie ma zajęć: ziglang.org
andrewrk 30.09.17

1
W niektórych przypadkach przydatne są klasy kontenerowe IMO (posiadające tylko metody statyczne).
AarCee

Odpowiedzi:


272

Jeśli szukasz sposobu zastosowania słowa kluczowego „static” do klasy, jak na przykład w C #, nie będziesz w stanie tego zrobić bez użycia Managed C ++.

Ale wygląd próbki wystarczy utworzyć publiczną metodę statyczną na obiekcie BitParser. Tak jak:

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

Możesz użyć tego kodu, aby wywołać metodę w taki sam sposób, jak kod przykładowy.

Mam nadzieję, że to pomaga! Twoje zdrowie.


2
OJ, masz błąd składniowy . Statyczne słowo kluczowe powinno być używane tylko w definicji klasy, a nie w definicji metody.
andrewrk

90
Aby wyjaśnić swój zamiar w tym podejściu, możesz dodatkowo użyć prywatnego konstruktora. private: BitParser() {}Uniemożliwi to każdemu tworzenie instancji.
Danvil

7
Bezpieczeństwo wątku @MoatazElmasry stanowi problem podczas udostępniania stanu. W powyższej implementacji nie ma współdzielonego stanu, więc nie może być żadnych problemów z bezpieczeństwem wątków ... chyba że jesteś wystarczająco głupi, aby używać statyki wewnątrz tych funkcji. Więc tak, powyższy kod jest bezpieczny dla wątków, po prostu utrzymuj trwały stan z dala od swoich funkcji i jesteś dobry.
Dz.U.

@MoatazElmasry Incorrect. Dwa wątki nie mogą modyfikować niestatycznych zmiennych lokalnych w funkcji statycznej.
Dz.U.

12
Jeśli C ++ 11, argumentowałbym, że lepiej BitParser() = delete;odpowiednio przekazać zamiar usunięcia konstruktora (nie tylko ukrywanie go jako private).
feniks

247

Zastanów się nad rozwiązaniem Matta Price'a .

  1. W C ++ „klasa statyczna” nie ma znaczenia. Najbliższa rzecz to klasa posiadająca wyłącznie statyczne metody i elementy.
  2. Korzystanie z metod statycznych tylko cię ogranicza.

To, czego chcesz, to wyrażone w semantyce C ++, aby umieścić swoją funkcję (ponieważ jest to funkcja) w przestrzeni nazw.

Edytuj 11.11.2011

W C ++ nie ma „klasy statycznej”. Najbliższą koncepcją byłaby klasa posiadająca wyłącznie metody statyczne. Na przykład:

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

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

Musisz jednak pamiętać, że „klasy statyczne” to włamania w językach podobnych do Javy (np. C #), które nie mogą mieć funkcji nie będących członkami, więc zamiast tego muszą przenieść je do klas jako metody statyczne.

W C ++ tak naprawdę chcesz funkcji nieosobistej, którą zadeklarujesz w przestrzeni nazw:

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

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

Dlaczego?

W C ++ przestrzeń nazw ma większą moc niż klasy dla wzorca „Java static method”, ponieważ:

  • metody statyczne mają dostęp do klas prywatnych symboli
  • prywatne metody statyczne są nadal widoczne (jeśli niedostępne) dla wszystkich, co nieco narusza enkapsulację
  • metody statyczne nie mogą być deklarowane w przód
  • metody statyczne nie mogą być przeciążone przez użytkownika klasy bez modyfikacji nagłówka biblioteki
  • nic nie da się zrobić za pomocą metody statycznej, której nie da się zrobić lepiej niż funkcja nie będąca członkiem (prawdopodobnie przyjacielem) w tej samej przestrzeni nazw
  • przestrzenie nazw mają własną semantykę (można je łączyć, mogą być anonimowe itp.)
  • itp.

Wniosek: Nie kopiuj / wklej tego wzorca Java / C # w C ++. W Javie / C # wzorzec jest obowiązkowy. Ale w C ++ jest to zły styl.

Edytuj 2010-06-10

Argumentowano za metodą statyczną, ponieważ czasami trzeba użyć statycznej zmiennej członka prywatnego.

Nie zgadzam się, jak pokazano poniżej:

Rozwiązanie „Statyczny członek prywatny”

// HPP

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

Po pierwsze, myGlobal nazywa się myGlobal, ponieważ wciąż jest globalną zmienną prywatną. Spojrzenie na źródło CPP wyjaśni, że:

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

Na pierwszy rzut oka fakt, że bezpłatna funkcja barC nie może uzyskać dostępu do Foo :: myGlobal, wydaje się dobrą rzeczą z punktu widzenia enkapsulacji ... Fajnie, ponieważ ktoś patrząc na HPP nie będzie w stanie (chyba, że ​​ucieknie się do sabotażu) uzyskać dostęp Foo :: myGlobal.

Ale jeśli przyjrzysz się temu uważnie, okaże się, że jest to kolosalny błąd: nie tylko twoja prywatna zmienna musi być nadal zadeklarowana w HPP (a więc widoczna dla całego świata, mimo że jest prywatna), ale musisz zadeklarować w tym samym HPP wszystkie (jak we WSZYSTKICH) funkcjach, które będą uprawnione do dostępu do niego !!!

Zatem używanie prywatnego statycznego członka jest jak chodzenie nago na zewnątrz z listą kochanków wytatuowaną na skórze: Nikt nie jest upoważniony do dotykania, ale każdy jest w stanie zerknąć. I bonus: każdy może mieć nazwiska osób upoważnionych do korzystania z twoich uprawnień.

private rzeczywiście ... :-D

Rozwiązanie „Anonimowe przestrzenie nazw”

Anonimowe przestrzenie nazw będą miały tę zaletę, że uczynią rzeczy prywatnymi naprawdę prywatnymi.

Najpierw nagłówek HPP

// HPP

namespace Foo
{
   void barA() ;
}

Dla pewności, że zauważyłeś: Nie ma bezużytecznej deklaracji barB ani myGlobal. Co oznacza, że ​​nikt nie czytający nagłówka nie wie, co kryje się za paskiem A.

Następnie 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 !!!
}

Jak widać, podobnie jak tak zwana deklaracja „klasy statycznej”, fooA i fooB nadal mają dostęp do myGlobal. Ale nikt inny nie może. I nikt poza CPP nie wie, że fooB i myGlobal nawet istnieją!

W przeciwieństwie do „klasy statycznej” chodzącej nago z książką adresową wytatuowaną na skórze, „anonimowa” przestrzeń nazw jest w pełni ubrana , co wydaje się znacznie lepiej zamknięte AFAIK.

Czy to naprawdę ma znaczenie?

O ile użytkownicy Twojego kodu nie są sabotażystami (pozwolę ci, jako ćwiczenie, dowiedzieć się, jak można uzyskać dostęp do prywatnej części klasy publicznej za pomocą brudnego, niezdefiniowanego zachowania hack ...), co to privatejest private, nawet jeśli to jest widoczny w privatesekcji klasy zadeklarowanej w nagłówku.

Jeśli jednak chcesz dodać kolejną „funkcję prywatną” z dostępem do prywatnego członka, nadal musisz ją zadeklarować na całym świecie, modyfikując nagłówek, co jest moim zdaniem paradoksem: jeśli zmienię implementację mój kod (część CPP), a następnie interfejs (część HPP) NIE powinien się zmienić. Cytując Leonidasa: „ To jest ENCAPSULATION!

Edytuj 2014-09-20

Kiedy klasy metody statyczne są w rzeczywistości lepsze niż przestrzenie nazw z funkcjami nieczłonkowskimi?

Gdy musisz zgrupować funkcje i nakarmić tę grupę szablonem:

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

Ponieważ jeśli klasa może być parametrem szablonu, przestrzenie nazw nie mogą.


3
GCC obsługuje -fno-access-control, którego można użyć w testach jednostek whitebox, aby uzyskać dostęp do członków klasy prywatnej. To jest jedyny powód, dla którego mogę wymyślić użycie członka klasy zamiast anonimowego / statycznego globalnego w implementacji.
Tom

8
@Tom: Rozwiązaniem wieloplatformowym byłoby dodanie następującego kodu #define private publicw nagłówkach ... ^ _ ^ ...
paercebal,

1
@Tom: w każdym razie, IMHO, nawet biorąc pod uwagę testy jednostkowe, wady „pokazywania zbyt wielu rzeczy” przeważają nad zaletami. Wydaje mi się, że alternatywnym rozwiązaniem byłoby umieszczenie kodu do przetestowania w funkcji przyjmującej potrzebne parametry (i nie więcej) w utilitiesprzestrzeni nazw. W ten sposób, ta funkcja może być jednostka sprawdzone i nadal nie mają specjalnego dostępu do członków prywatnych (jak są one podane jako parametry w wywołaniu funkcji) ...
paercebal

@paercebal Mam zamiar wskoczyć na pokład twojego statku, ale mam jedną ostatnią rezerwację. Jeśli ktoś wskoczy w twoją namespacewolę, nie uzyska dostępu do twoich global, choć ukrytych, członków? Oczywiście musieliby zgadywać, ale jeśli celowo nie zaciemniacie swojego kodu, nazwy zmiennych są dość łatwe do odgadnięcia.
Zak.

@Zak: Owszem, mogli, ale tylko próbując to zrobić w pliku CPP, w którym deklarowana jest zmienna myGlobal. Chodzi o lepszą widoczność niż dostępność. W klasie statycznej zmienna myGlobal jest prywatna, ale nadal widoczna. Nie jest to tak ważne, jak się wydaje, ale mimo to w bibliotece DLL pokazanie symbolu, który powinien być prywatny dla biblioteki DLL w eksportowanym nagłówku, może być niezręczne ... W przestrzeni nazw myGlobal istnieje tylko w pliku CPP (ty może nawet pójść dalej i uczynić go statycznym). Ta zmienna nie pojawia się w nagłówkach publicznych.
paercebal,

63

Możesz także utworzyć bezpłatną funkcję w przestrzeni nazw:

W BitParser.h

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

W BitParser.cpp

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

Zasadniczo byłby to preferowany sposób pisania kodu. Gdy obiekt nie jest potrzebny, nie używaj klasy.


1
W niektórych przypadkach możesz chcieć mieć enkapsulację danych, nawet jeśli klasa jest w większości „statyczna”. Dadzą ci to statyczni członkowie klasy prywatnej. Członkowie przestrzeni nazw są zawsze publiczni i nie mogą zapewniać hermetyzacji danych.
Torleif,

Jeśli zmienna „członek” jest deklarowana i dostępna tylko z pliku .cpp, jest bardziej prywatna niż zmienna prywatna zadeklarowana w pliku .h. NIE, że polecam tę technikę.
jmucchiello

3
@ Torleif: Mylisz się. przestrzenie nazw są lepsze do enkapsulacji niż statyczne prywatne elementy. Zobacz moją odpowiedź na demonstrację.
paercebal,

1
tak, ale w przestrzeni nazw należy zachować kolejność funkcji, w przeciwieństwie do klas z elementami statycznymi, na przykład void a () {b ();} b () {} spowoduje błąd w przestrzeni nazw, ale nie w klasie z członkowie statyczni
Moataz Elmasry

13

Jeśli szukasz sposobu zastosowania słowa kluczowego „static” do klasy, na przykład w języku C #

klasy statyczne są po prostu kompilatorem, który trzyma cię za rękę i powstrzymuje cię przed pisaniem metod / zmiennych instancji.

Jeśli po prostu napiszesz normalną klasę bez żadnych metod / zmiennych instancji, to jest to samo i tak właśnie zrobiłbyś w C ++


Nie narzekać (szczególnie na ciebie), ale staticdobrze byłoby trochę trzymać kompilatora za rękę, aby powstrzymać mnie od pisania lub wycinania / wklejania słowa 200 razy.
3Dave

Zgoda - ale klasa statyczna w C # też tego nie robi. Po prostu nie kompiluje się, gdy zapomnisz wkleić tam statyczny :-)
Orion Edwards

Tak - w porządku. Moje makra są wyświetlane. Szczerze mówiąc, jeśli zadeklaruję klasę jako statyczną, kompilator powinien zgłosić błąd tylko wtedy, gdy spróbuję go utworzyć. Reguły, które wymagają ode mnie powtarzania się, są wstrętne i powinny stać się pierwszym, gdy nadejdzie rewolucja.
3Dave

11

W C ++ chcesz utworzyć funkcję statyczną klasy (nie klasę statyczną).

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

Powinieneś być w stanie wywołać funkcję przy użyciu BitParser :: getBitAt () bez tworzenia obiektu, który, jak zakładam, jest pożądanym rezultatem.


11

Czy mogę napisać coś takiego static class?

Nie , zgodnie ze standardowym projektem C ++ 11 N3337, załącznik C 7.1.1:

Zmiana: W C ++ specyfikatory statyczne lub zewnętrzne mogą być stosowane tylko do nazw obiektów lub funkcji. Używanie tych specyfikatorów z deklaracjami typów jest nielegalne w C ++. W C te specyfikatory są ignorowane, gdy są używane w deklaracjach typu. Przykład:

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

Uzasadnienie: Specyfikatory klas pamięci nie mają żadnego znaczenia, gdy są powiązane z typem. W C ++ członkowie klasy mogą być deklarowani za pomocą statycznego specyfikatora klasy pamięci. Dopuszczenie specyfikatorów klas pamięci w deklaracjach typu może sprawić, że kod będzie mylący dla użytkowników.

I jak struct, classjest także deklaracja typu.

To samo można wywnioskować, spacerując po drzewie składni w załączniku A.

Warto zauważyć, że static structbyło to legalne w C, ale nie miało żadnego skutku: dlaczego i kiedy używać struktur statycznych w programowaniu w C?


6

Jak zauważono tutaj, lepszym sposobem na osiągnięcie tego w C ++ może być użycie przestrzeni nazw. Ale ponieważ nikt nie wspomniał finaltutaj o tym słowie kluczowym, piszę, static classjak mógłby wyglądać bezpośredni odpowiednik z C # w C ++ 11 lub nowszym:

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

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

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

5

Możesz „mieć” klasę statyczną w C ++, jak wspomniano wcześniej, klasa statyczna to taka, która nie ma żadnych instancji jej obiektów. W C ++ można to uzyskać, deklarując konstruktor / destruktor jako prywatny. Wynik końcowy jest taki sam.


To, co sugerujesz, może stworzyć klasę singleton, ale nie jest to to samo, co klasa statyczna.
ksinkar

4

W Managed C ++ składnia klasy statycznej to: -

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

... lepiej późno niż wcale...


3

Jest to podobne do sposobu, w jaki C # robi to w C ++

W pliku C #.cs możesz mieć prywatną zmienną wewnątrz funkcji publicznej. Znajdując się w innym pliku, możesz go użyć, wywołując przestrzeń nazw z funkcją jak w:

MyNamespace.Function(blah);

Oto jak naśladować to samo w 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
Ale wszyscy mogą przejść do TheDataToBeHidden -> To nie jest rozwiązanie
Guy L

3

W przeciwieństwie do innych zarządzanych języków programowania, „klasa statyczna” nie ma znaczenia w C ++. Możesz skorzystać ze statycznej funkcji członka.


0

Jednym z przypadków, w których przestrzenie nazw mogą nie być tak przydatne do osiągnięcia „klas statycznych”, jest użycie tych klas do uzyskania kompozycji zamiast dziedziczenia. Przestrzenie nazw nie mogą być przyjaciółmi klas, a zatem nie mogą uzyskać dostępu do prywatnych członków klasy.

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

Jedną (z wielu) alternatywą, ale najbardziej (moim zdaniem) eleganckim (w porównaniu do używania przestrzeni nazw i prywatnych konstruktorów do emulacji statycznego zachowania) sposobem na osiągnięcie „klasy, której nie można utworzyć” w C ++ byłoby zadeklarować funkcję czysto wirtualną za pomocą privatemodyfikatora dostępu.

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

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

Jeśli używasz C ++ 11, możesz zrobić wszystko, aby upewnić się, że klasa nie jest dziedziczona (w celu czystej emulacji zachowania klasy statycznej), używając finalspecyfikatora w deklaracji klasy, aby ograniczyć inne klasy przed dziedziczeniem .

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

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

Choć może to zabrzmieć głupio i nielogicznie, C ++ 11 dopuszcza deklarację „funkcji wirtualnej, której nie można zastąpić”, której można użyć obok zadeklarowania klasy finalczysto i w pełni zaimplementować zachowanie statyczne, ponieważ powoduje to powstanie wyniku klasy, aby nie była dziedziczona, a funkcja manekina nie powinna być w żaden sposób nadpisywana.

// 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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.