Як створити статичний клас у C ++?


263

Як створити статичний клас у C ++? Я повинен вміти робити щось на кшталт:

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

Якщо припустити, що я створив BitParserклас. Як BitParserвиглядатиме визначення класу?


198
Так. Ще в давні часи ми просто називали це "функцією". Ви, діти, сьогодні зі своїми шаленими "просторами імен". Гей, зійди з моєї галявини! <трясе кулак>
бродяга

7
@Vagrant функція всередині простору імен залишається функцією. Функція, що належить до класу, називається методом. Якщо це статичний метод, ви викликаєте його аналогічно, як якщо б це була функція всередині простору імен.

48
Через 5 років і зараз я ставкою до @Vagrant. Яке дурне питання!
andrewrk

16
9 років потому, і тепер у мене є своя мова програмування, яка навіть не має класів: ziglang.org
andrewrk

1
У певних випадках корисні контейнерні класи IMO (що мають лише статичні методи).
AarCee

Відповіді:


272

Якщо ви шукаєте спосіб застосування "статичного" ключового слова до класу, як, наприклад, у C #, тоді ви не зможете без використання керованого C ++.

Але виглядає ваш зразок, вам просто потрібно створити загальнодоступний статичний метод на вашому об’єкті BitParser. Так:

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

Ви можете використовувати цей код для виклику методу так само, як і ваш код прикладу.

Сподіваюся, що це допомагає! Ура.


2
Ой, у вас синтаксична помилка . Статичне ключове слово слід використовувати лише у визначенні класу, а не у визначенні методу.
andrewrk

90
Щоб зрозуміти свій намір у цьому підході, ви можете додатково скористатися приватним конструктором. private: BitParser() {}Це не дозволить комусь створити екземпляри.
Данвіл

7
@MoatazElmasry безпека ниток - це проблема, коли ви ділитесь із загальним станом. У вищезазначеній реалізації немає спільного стану, тому не може виникнути жодних проблем із безпекою потоку ... якщо тільки ви не дурні, щоб використовувати статику всередині цих функцій. Так що так, наведений вище код є безпечним для потоків, просто утримуйте стійкий стан від своїх функцій і ви добре
ОВ.

@MoatazElmasry Неправильно. Два потоки не можуть змінювати нестатичні локальні змінні в статичній функції.
ОВ.

12
Якщо C ++ 11, я вважаю, що краще BitParser() = delete;правильно передати намір видалити конструктор (а не просто приховати його як private).
фенікс

247

Розгляньте рішення Метта Прайса .

  1. У C ++ "статичний клас" не має значення. Найближча річ - клас із лише статичними методами та членами.
  2. Використання статичних методів лише обмежить вас.

Що ви хочете, виражене в семантиці C ++, щоб поставити функцію (вона є функцією) в просторі імен.

Редагувати 2011-11-11

У C ++ немає "статичного класу". Найближчим поняттям буде клас із лише статичними методами. Наприклад:

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

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

Але ви повинні пам’ятати, що «статичні класи» - це хаки в мовах, подібних до Java (наприклад, C #), які не можуть мати функцій, що не належать до членів, тому їм доведеться замість цього переміщувати їх всередині класів як статичні методи.

У C ++ те, що ви дійсно хочете, - це нечленова функція, яку ви оголосите в просторі імен:

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

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

Чому так?

У C ++ простір імен є більш потужним, ніж класи для шаблону "статичний метод Java", оскільки:

  • статичні методи мають доступ до приватних символів класів
  • приватні статичні методи все ще помітні (якщо недоступні) для всіх, що дещо порушує інкапсуляцію
  • статичні методи не можуть бути оголошені вперед
  • статичні методи не можуть бути перевантажені користувачем класу без зміни заголовка бібліотеки
  • нічого не можна зробити статичним методом, що не можна зробити краще, ніж функція (можливо друга), яка не є членом, в тому ж просторі імен
  • простори імен мають власну семантику (їх можна комбінувати, вони можуть бути анонімними тощо)
  • тощо.

Висновок: Не копіюйте та не вставляйте шаблон Java / C # в C ++. У Java / C # шаблон є обов'язковим. Але в C ++ це поганий стиль.

Редагувати 2010-06-10

Був аргумент на користь статичного методу, оскільки іноді потрібно використовувати статичну змінну приватного члена.

Я дещо не згоден, як показано нижче:

Рішення "Статичний приватний член"

// HPP

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

По-перше, myGlobal називається myGlobal, оскільки він все ще є глобальною приватною змінною. Погляд на джерело CPP уточнить, що:

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

На перший погляд, факт вільної функції barC не може отримати доступ до Foo :: myGlobal здається хорошою з точки зору інкапсуляції ... Це здорово, тому що хтось, хто дивиться на ГЕС, не зможе (якщо не вдасться до саботажу) отримати доступ Foo :: myGlobal.

Але якщо ви уважно придивитесь до цього, то виявите, що це колосальна помилка: не тільки ваша приватна змінна все ще повинна бути оголошена в ГЕС (і так, видима для всього світу, незважаючи на приватність), але ви повинні заявити в тій же ГЕС всі (як і ВСІ) функції, які матимуть право доступу до неї !!!

Тож використання приватного статичного члена - це як виходити на вулицю з оголеним списком ваших закоханих, татуйованих на вашій шкірі: ніхто не має права торкатися, але кожен може зазирнути. І бонус: кожен може мати імена тих, хто має право грати з вашими призами.

private справді ... :-D

Рішення "Анонімні простори імен"

Анонімні простори імен матимуть перевагу в тому, щоб зробити речі справді приватними.

По-перше, заголовок ГЕС

// HPP

namespace Foo
{
   void barA() ;
}

Просто для переконання, що ви зауважили: Не існує марної декларації barB чи myGlobal. Що означає, що ніхто не читає заголовка не знає, що ховається за барA.

Потім 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 !!!
}

Як бачимо, як і так зване декларування "статичного класу", fooA і fooB все ще можуть отримати доступ до myGlobal. Але ніхто більше не може. І ніхто більше поза цією CPP не знає, що fooB і myGlobal навіть існують!

На відміну від "статичного класу", що ходить по ню з татуйованою на її шкірі адресною книжкою, "анонімний" простір імен повністю одягнений , що здається набагато краще зафіксованим AFAIK.

Це насправді має значення?

Якщо користувачі вашого коду не є диверсантами (я дозволю вам, як вправу дізнатись, як можна отримати доступ до приватної частини публічного класу, використовуючи брудний не визначений хакер ...), що privateтаке private, навіть якщо це видно в privateрозділі класу, оголошеного в заголовку.

Тим не менш, якщо вам потрібно додати ще одну "приватну функцію" з доступом до приватного члена, ви все одно повинні оголосити її всьому світу, змінивши заголовок, що є парадоксам, що стосується мене: Якщо я змінити реалізацію мій код (CPP частина), то інтерфейс (частина HPP) НЕ повинен змінюватися. Цитуючи Леонідаса: " Це ЕНКАПСУЛАЦІЯ! "

Редагувати 2014-09-20

Коли статичні методи класів насправді кращі, ніж простори імен з не членами функцій?

Коли потрібно згрупувати функції та подати групу до шаблону:

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

Тому що, якщо клас може бути параметром шаблону, простори імен не можуть.


3
GCC підтримує -fno-доступ-контроль, який може бути використаний у тестах одиниць білого ящика для доступу до членів приватного класу. Ось про єдину причину, яку я можу думати, щоб виправдати використання члена класу замість анонімного / статичного глобального в реалізації.
Том

8
@Tom: Кросплатформенним рішенням було б додати наступний код #define private publicу заголовки ... ^ _ ^ ...
paercebal

1
@Tom: так чи інакше, IMHO, навіть враховуючи тестування одиниць, мінуси "показу занадто багато речей" переважують плюси. Я думаю, альтернативним рішенням буде поставити тестований код у функцію, яка потребує необхідних параметрів (і не більше) у utilitiesпросторі імен. Таким чином, ця функція може бути перевірена одиницями, і все ще не мати спеціального доступу до приватних членів (оскільки вони задаються як параметри при виклику функції) ...
paercebal

@paercebal Я збираюся стрибнути на борт вашого корабля, але у мене є одне остаточне застереження. Якщо хтось стрибає у вашій namespaceволі, він не отримає доступу до ваших global, хоч і прихованих, членів? Очевидно, їм доведеться здогадуватися, але якщо ви навмисно не заблукаєте кодом, імена змінних досить легко здогадатися.
Зак

@Zak: Дійсно, вони могли, але лише спробувавши це зробити у файлі CPP, де оголошена змінна myGlobal. Сенс у більшій видимості, ніж доступності. У статичному класі змінна myGlobal приватна, але все ще видима. Це не так важливо, як здається, але все ж, у DLL, показ символу, який повинен бути приватним для DLL у експортованому заголовку, може бути незручним ... У просторі імен myGlobal існує лише у файлі CPP (ви може навіть піти далі і зробити це статичним). Ця змінна не відображається у загальнодоступних заголовках.
paercebal

63

Ви також можете створити безкоштовну функцію в просторі імен:

У BitParser.h

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

У BitParser.cpp

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

Взагалі, це був би кращий спосіб написання коду. Коли немає необхідності в об'єкті, не використовуйте клас.


1
У деяких випадках ви можете мати інкапсуляцію даних, навіть якщо клас здебільшого "статичний". Статичні члени приватного класу вам це дадуть. Учасники простору імен завжди є загальнодоступними і не можуть забезпечити інкапсуляцію даних.
Торлейф

Якщо до "var" var оголошено лише доступ до нього .cpp-файл, він є більш приватним, ніж приватний var, оголошений у файлі .h. НЕ що я рекомендую цю техніку.
jmucchiello

3
@Torleif: Ви помиляєтесь. простори імен краще для інкапсуляції, ніж статичні приватні члени. Дивіться мою відповідь для демонстрації.
paercebal

1
так, але в просторі імен ви повинні зберігати порядок функцій на відміну від класу зі статичними членами, наприклад void a () {b ();} b () {} призведе до помилки в просторі імен, але не в класі з члени статики
Моатас Ельмасрі

13

Якщо ви шукаєте спосіб застосування "статичного" ключового слова до класу, як, наприклад, у C #

статичні класи - це лише компілятор, який тримає вас у руках і не дозволяє вам писати будь-які методи / змінні.

Якщо ви просто пишете звичайний клас без будь-яких методів / змінних примірників, це те саме, і це ви б робили в C ++


Не скаржитися (особливо на вас), але якийсь компілятор, який тримає мене від написання чи скорочення / вставки цього слова, staticбуло б добре.
3Dave

Погоджено - але статичний клас у C # теж не робить цього. Він просто не вдається зібрати, коли ви забудете вставити статику туди :-)
Orion Edwards

Так - досить справедливо. Показують мої макроси. Чесно кажучи, якщо я оголошу клас як статичний, компілятор повинен видавати помилку лише в тому випадку, якщо я спробую її інстанціювати. Правила, які вимагають від мене повторення, - нечесні і повинні бути першими проти стіни, коли настане революція.
3Dave

11

У C ++ ви хочете створити статичну функцію класу (а не статичний клас).

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

Тоді ви зможете викликати функцію за допомогою BitParser :: getBitAt (), не ініціюючи об'єкт, який я вважаю, є бажаним результатом.


11

Чи можу я написати щось на кшталт static class?

Ні , згідно з проектом додатка C 7.1.1 стандарту C ++ 11 N3337 :

Зміна: у C ++ статичні або зовнішні специфікатори можуть застосовуватися лише до імен об'єктів або функцій. Використання цих специфікаторів з деклараціями типу незаконно в C ++. У C ці специфікатори ігноруються при використанні в деклараціях типу. Приклад:

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

Обгрунтування: специфікатори класу зберігання не мають жодного значення, коли асоціюються з типом. У C ++ члени класу можуть бути оголошені статичним специфікатором класу зберігання. Дозволення специфікаторів класу зберігання для декларацій типу може зробити код заплутаним для користувачів.

І, як struct, classтакож є декларація про тип.

Те саме можна визначити, пройшовши синтаксичне дерево у Додатку А.

Цікаво відзначити, що це static structбуло законно в C, але не мало ефекту: навіщо і коли використовувати статичні структури в програмуванні на C?


6

Як було зазначено тут, кращим способом досягнення цього в C ++ може бути використання просторів імен. Але оскільки тут ніхто не згадав finalключове слово, я публікую, як static classвиглядатиме прямий еквівалент C # у C ++ 11 або новіших версіях:

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

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

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

5

Ви можете "мати" статичний клас у C ++, як згадувалося раніше, статичний клас - це той, у якого немає жодних об'єктів, які його інстанціювали. У C ++ це можна отримати, оголосивши конструктор / деструктор приватним. Кінцевий результат той самий.


Те, що ви пропонуєте, може створити однотонний клас, але це не те саме, що статичний клас.
ksinkar

4

У керованому C ++ синтаксисом статичного класу є: -

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

... краще пізно, ніж ніколи...


3

Це схоже на спосіб C # робити це в C ++

У C # file.cs ви можете мати приватний var всередині публічної функції. Якщо в іншому файлі, ви можете використовувати його, викликаючи простір імен з функцією, як у:

MyNamespace.Function(blah);

Ось як додати те саме в 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
Але кожен може зайнятись TheDataToBeHidden -> Це не рішення
Хлопець L

3

На відміну від інших керованих мов програмування, "статичний клас" не має значення на C ++. Ви можете використовувати статичну функцію члена.


0

Один з випадків, коли простори імен можуть бути не настільки корисними для досягнення "статичних класів", - це при використанні цих класів для досягнення складу над успадкуванням. Простори імен не можуть бути друзями класів і тому не можуть отримати доступ до приватних членів класу.

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

Один (із безлічі) альтернативних, але самий (на мій погляд) елегантний (порівняно з використанням просторів імен та приватних конструкторів для імітації статичної поведінки), спосіб досягти поведінки "класу, який неможливо інстанціювати" у C ++ оголосити фіктивну чисту віртуальну функцію за допомогою privateмодифікатора доступу.

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

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

Якщо ви використовуєте C ++ 11, ви можете пройти додаткову милю, щоб переконатися, що клас не успадковується (щоб чисто імітувати поведінку статичного класу), використовуючи finalспецифікатор у декларації класу, щоб обмежити інші класи від успадкування його .

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

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

Як би нерозумно і нелогічно це не звучало, C ++ 11 дозволяє оголосити "чисту віртуальну функцію, яку неможливо змінити", яку ви можете використовувати разом з декларуванням класу finalдля чистої та повної реалізації статичної поведінки, оскільки це призводить до результату клас, який не має бути успадкованим, а фіктивна функція ні в якому разі не перекривається.

// 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.