Как вы создаете статический класс в C ++?

264

Как вы создаете статический класс в C ++? Я должен быть в состоянии сделать что-то вроде:

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

Предполагая, что я создал BitParserкласс. Как будет BitParserвыглядеть определение класса?

andrewrk
источник
198
Ага. В старые времена мы просто называли это «функцией». Вы, дети, сегодня со своими сумасшедшими "пространствами имен". Эй, сойди с моей лужайки! <Встряхивает кулак>
Бродяга
7
@ Vagrant функция внутри пространства имен по-прежнему является функцией. Функция, которая принадлежит классу, называется методом. Если это статический метод, вы вызываете его аналогично, как если бы это была функция внутри пространства имен.
48
Спустя 5 лет и сейчас я перешёл на сторону @Vagrant. Что за глупый вопрос!
Andrewrk
16
Спустя 9 лет, и теперь у меня есть свой собственный язык программирования, который даже не имеет классов: ziglang.org
andrewrk
1
Контейнерные классы IMO (имеющие только статические методы) полезны в некоторых случаях.
AarCee

Ответы:

272

Если вы ищете способ применения «статического» ключевого слова к классу, как, например, в C #, вы не сможете без использования Managed 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;
}

Вы можете использовать этот код для вызова метода так же, как ваш пример кода.

Надеюсь, это поможет! Приветствия.

OJ.
источник
2
OJ, у вас есть синтаксическая ошибка . Ключевое слово static должно использоваться только в определении класса, а не в определении метода.
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

Был аргумент в пользу статического метода, потому что иногда нужно использовать статическую закрытую переменную-член.

Я не согласен, как показано ниже:

Решение "Static private member"

// 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. Это означает, что никто, читающий заголовок, не знает, что скрыто за barA.

Затем 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 !!!

Потому что, если класс может быть параметром шаблона, пространства имен не могут.

paercebal
источник
3
GCC поддерживает -fno-access-control, который можно использовать в модульных тестах whitebox для доступа к закрытым членам класса. Это единственная причина, по которой я могу оправдать использование члена класса вместо анонимного / статического глобального в реализации.
Том
8
@Tom: кроссплатформенным решением было бы добавить следующий код #define private publicв заголовки ... ^ _ ^ ...
paercebal
1
@Tom: во всяком случае, ИМХО, даже учитывая модульное тестирование, минусы «показа слишком большого количества материала» перевешивают плюсы. Я предполагаю, что альтернативным решением было бы поместить тестируемый код в функцию, принимающую необходимые параметры (и не более) в 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
В некоторых случаях вам может потребоваться инкапсуляция данных, даже если класс в основном «статический». Статические члены частного класса дадут вам это. Члены пространства имен всегда общедоступны и не могут обеспечить инкапсуляцию данных.
Торлейф
Если переменная "member" объявлена ​​и доступна только из файла .cpp, она является более закрытой, чем частная переменная, объявленная в файле .h. НЕ то, что я рекомендую эту технику.
jmucchiello
3
@ Torleif: Вы ошибаетесь. пространства имен лучше подходят для инкапсуляции, чем статические закрытые члены. Смотрите мой ответ для демонстрации.
paercebal
1
да, но в пространстве имен вы должны поддерживать порядок функций, в отличие от класса со статическими членами, например, void a () {b ();} b () {} приведет к ошибке в пространстве имен, но не в классе с статические участники
Moataz Elmasry
13

Если вы ищете способ применения «статического» ключевого слова к классу, как вы можете, например, в C #

статические классы - это просто компилятор, который держит вас за руку и мешает писать любые методы / переменные экземпляра.

Если вы просто пишете обычный класс без каких-либо методов / переменных экземпляра, это то же самое, и это то, что вы делаете в C ++.

Орион Эдвардс
источник
Не жаловаться (особенно на вас), но staticбыло бы хорошо, если бы кто-то держал меня за компилятор, чтобы я не писал и не вырезал / вставил слово 200 раз.
3Dave
Согласен - но статический класс в C # тоже этого не делает. Он просто не скомпилируется, когда вы забыли вставить туда статические данные :-)
Orion Edwards
Да - достаточно честно. Мои макросы показывают. Честно говоря, если я объявляю класс статическим, компилятор должен выдавать ошибку только в том случае, если я пытаюсь его создать. Правила, которые требуют от меня повторения, неприятны и должны быть первыми, когда наступит революция.
3Dave
11

В C ++ вы хотите создать статическую функцию класса (не статический класс).

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

После этого вы сможете вызывать функцию, используя BitParser :: getBitAt (), не создавая объект, который, как я предполагаю, является желаемым результатом.

Филип Рейнольдс
источник
11

Могу ли я написать что-то вроде static class?

Нет , согласно проекту стандарта C ++ 11 N3337, Приложение C 7.1.1:

Изменение: в 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 ++ это можно получить, объявив конструктор / деструктор как закрытый. Конечный результат такой же.

Нецер
источник
То, что вы предлагаете, может создать одноэлементный класс, но он не совпадает со статическим классом.
Ксинкар
4

В Managed C ++ синтаксис статического класса:

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

... лучше поздно, чем никогда...

Малк Б
источник
3

Это похоже на способ C # сделать это в C ++

В C # file.cs вы можете иметь приватную переменную внутри публичной функции. Когда в другом файле вы можете использовать его, вызывая пространство имен с помощью функции, как в:

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 -> Это не решение проблемы
Guy 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
Геката
источник