Как создать статический класс на C++? Я должен уметь делать что-то вроде:
cout << "bit 5 is " << BitParser::getBitAt(buffer, 5) << endl;
Предполагая, что я создал класс BitParser
. Как бы выглядело определение класса BitParser
?
@ superjoe30 Насколько я знаю, есть одно хорошее применение для "статического" класса: специализация перегруженной функции-шаблона - см. "Мораль № 2" здесь.
Контейнероподобные классы IMO (имеющие только статические методы) полезны в определенных случаях.
Шаблоны статических классов можно использовать для удаления избыточных объявлений типов в нескольких шаблонах классов.
В C++ вы хотите создать статическую функцию класса (а не статический класс).
class BitParser {
public:
...
static ... getBitAt(...) {
}
};
Затем вы сможете вызвать функцию с помощью BitParser :: getBitAt () без создания экземпляра объекта, который, как я полагаю, является желаемым результатом.
Если вы ищете способ применения ключевого слова static к классу, как, например, в 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;
}
Вы можете использовать этот код для вызова метода так же, как ваш пример кода.
Надеюсь, это поможет! Ваше здоровье.
Чтобы прояснить ваше намерение в этом подходе, вы можете дополнительно использовать частный конструктор. private: BitParser() {}
Это предотвратит создание экземпляров кем-либо.
OJ, у вас есть синтаксическая ошибка. Ключевое слово static следует использовать только в определении класса, а не в определении метода.
@MoatazElmasry потокобезопасность - проблема, когда вы делитесь состоянием. В приведенной выше реализации нет общего состояния, поэтому не может быть никаких проблем с безопасностью потоков ... если только вы не настолько глупы, чтобы использовать статику внутри для этих функций. Так что да, приведенный выше код является потокобезопасным, просто держите постоянное состояние вне ваших функций, и все хорошо.
@MoatazElmasry Неверно. Два потока не могут изменять нестатические локальные переменные в статической функции.
Если вы пытаетесь добиться аналогичного поведения Статические классы C#, вы должны пометить класс как запечатанный с помощью ключевого слова final
(из C++ 11), чтобы предотвратить наследование любого другого класса.
Если бы C++ 11, я бы сказал, что лучше BitParser() = delete;
правильно передать намерение удалить конструктор (а не просто скрыть его как private
).
If you're looking for a way of applying the "static" keyword to a class, like you can in C# for example
статические классы - это просто компилятор, который держит вас за руку и не дает вам писать какие-либо методы / переменные экземпляра.
Если вы просто напишете обычный класс без каких-либо методов / переменных экземпляра, это то же самое, и это то, что вы бы сделали на C++.
Не жаловаться (особенно на вас), но некоторая поддержка компилятора, чтобы я не написал или не вырезал / не вставлял слово static
200 раз, было бы хорошо.
Согласен, но статический класс в C# тоже этого не делает. Он просто не компилируется, когда вы забываете вставить туда статику :-)
Ага - достаточно честно. Мои макросы отображаются. Честно говоря, если я объявлю класс статическим, компилятор должен выдать ошибку, только если я попытаюсь создать его экземпляр. Правила, требующие от меня повторения, отвратительны и должны быть первыми против стены, когда наступит революция.
Вы также можете создать бесплатную функцию в пространстве имен:
В BitParser.h
namespace BitParser
{
bool getBitAt(int buffer, int bitIndex);
}
В BitParser.cpp
namespace BitParser
{
bool getBitAt(int buffer, int bitIndex)
{
//get the bit :)
}
}
В общем, это предпочтительный способ написания кода. Когда в объекте нет необходимости, не используйте класс.
В некоторых случаях вам может потребоваться инкапсуляция данных, даже если класс в основном «статический». Статические частные члены класса дадут вам это. Члены пространства имен всегда являются общедоступными и не могут обеспечивать инкапсуляцию данных.
Если переменная "член" объявляется только из файла .cpp, и доступ к ней осуществляется только из файла .cpp, он более частный, чем частная переменная, объявленная в файле .h. НЕ то, что я рекомендую эту технику.
@Torleif: Вы ошибаетесь. пространства имен лучше подходят для инкапсуляции, чем статические частные члены. См. Мой ответ для демонстрации.
да, но в пространстве имен вы должны соблюдать порядок функций, в отличие от класса со статическими членами, например void a () {b ();} b () {} приведет к ошибке в пространстве имен, но не в классе с статические члены
Рассмотрим Решение Мэтта Прайса.
Вы хотите, выражаясь семантикой C++, поместить вашу функцию (для нее является функция) в пространство имен.
В 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++ это плохой стиль.
Был аргумент в пользу статического метода, потому что иногда нужно использовать статическую частную переменную-член.
Я несколько не согласен, как показано ниже:
// 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, кажется хорошим с точки зрения инкапсуляции ... Это круто, потому что кто-то, смотрящий на HPP, не сможет (если не прибегнет к саботажу) получить доступ Foo :: myGlobal.
Но если вы внимательно посмотрите на это, вы обнаружите, что это колоссальная ошибка: в HPP должна быть объявлена не только ваша частная переменная (и, следовательно, видимая для всего мира, несмотря на то, что она является частной), но вы должны объявить в той же HPP все (как и во ВСЕХ) функции, которым будет разрешен к ней доступ !!!
Итак, использование частного статического члена похоже на прогулку обнаженной на улице со списком ваших любовников, вытатуированным на вашей коже: никто не имеет права прикасаться, но каждый может взглянуть на него. И бонус: каждый может иметь имена тех, кто имеет право играть с вашими личными вещами.
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.
Если пользователи вашего кода не являются саботажниками (я позволю вам в качестве упражнения найти, как можно получить доступ к частной части публичного класса с помощью взлома грязного поведения-undefined ...), то, что private
, есть private
, даже если он виден в разделе private
класса, объявленного в заголовке.
Тем не менее, если вам нужно добавить еще одну «частную функцию» с доступом к частному члену, вы все равно должны объявить ее всему миру, изменив заголовок, что, на мой взгляд, является парадоксом: Если я изменю реализацию своего кода (часть CPP), то интерфейс (часть HPP) НЕ должен измениться. Цитата Леонидаса: «Это ЗАКЛЮЧЕНИЕ! "
Когда статические методы классов действительно лучше, чем пространства имен с функциями, не являющимися членами?
Когда вам нужно сгруппировать функции и передать эту группу в шаблон:
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 !!!
Потому что, если класс может быть параметром шаблона, пространства имен не могут.
GCC поддерживает -fno-access-control, который можно использовать в модульных тестах белого ящика для доступа к другим частным членам класса. Это единственная причина, по которой я могу оправдать использование члена класса вместо анонимного / статического глобального в реализации.
@Tom: Кросс-платформенным решением было бы добавить следующий код #define private public
в заголовки ... ^ _ ^ ...
@Tom: в любом случае, IMHO, даже с учетом модульного тестирования, минусы "показывать слишком много вещей" перевешивают плюсы. Я предполагаю, что альтернативным решением было бы поместить тестируемый код в функцию, принимающую необходимые параметры (и не более) в пространстве имен utilities
. Таким образом, эта функция может быть протестирована на единицу и по-прежнему не имеет специального доступа к закрытым членам (поскольку они задаются как параметры при вызове функции) ...
@paercebal Я собираюсь прыгнуть на борт твоего корабля, но у меня есть последнее оговорка. Если кто-то запрыгнет в ваш namespace
, не получит ли он доступ к вашим global
, пусть и скрытым, членам? Очевидно, им придется угадывать, но если вы намеренно не запутаете свой код, имена переменных угадать довольно легко.
@Zak: Да, могли, но только пытаясь сделать это в файле CPP, где объявлена переменная myGlobal. Дело в большей наглядности, чем в доступности. В статическом классе переменная myGlobal является частной, но все же видимой. Это не так важно, как кажется, но все же в DLL отображение символа, который должен быть частным для DLL в экспортированном заголовке, может быть неудобным ... В пространстве имен myGlobal существует только в файле CPP (вы можно даже пойти дальше и сделать его статичным). Эта переменная не отображается в общедоступных заголовках.
@paercebal Значит, помещая их в анонимный namespace
, вы закрываете переменные? В своем ответе можете ли вы отойти от своей аналогии и объяснить механизм namespace
более детально (как в наложенных ограничениях)?
@paercebal, а также #define class struct
для перехвата членов, которые по умолчанию являются закрытыми в начале класса. Но похоже, что это может что-то сломать? Вы видите, как это могло быть?
Вы «можете» иметь статический класс в C++, как упоминалось ранее, статический класс - это тот, который не имеет каких-либо объектов, созданных для его создания. В C++ это можно получить, объявив конструктор / деструктор закрытым. Конечный результат такой же.
То, что вы предлагаете, может создать одноэлементный класс, но это не то же самое, что статический класс.
Это похоже на то, как это делается в 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();
Но каждый может зайти в The DataToBeHidden -> Это не решение
В Managed C++ синтаксис статического класса: -
public ref class BitParser abstract sealed
{
public:
static bool GetBitAt(...)
{
...
}
}
... лучше поздно, чем никогда...
В отличие от других языков управляемого программирования, «статический класс» НЕ имеет значения в C++. Вы можете использовать статическую функцию-член.
Можно написать что-нибудь вроде static class
?
Нет, согласно Приложению C 7.1.1 Стандартный проект C++ 11 N3337:
Change: In C++, the static or extern specifiers can only be applied to names of objects or functions. Using these specifiers with type declarations is illegal in C++. In C, these specifiers are ignored when used on type declarations. Example:
static struct S { // valid C, invalid in C++ int i; };
Rationale: Storage class specifiers don’t have any meaning when associated with a type. In C++, class members can be declared with the static storage class specifier. Allowing storage class specifiers on type declarations could render the code confusing for users.
Как и struct
, class
также является объявлением типа.
То же самое можно сделать, просмотрев дерево синтаксиса в Приложении A.
Интересно отметить, что static struct
был разрешен в C, но не имел никакого эффекта: Почему и когда использовать статические структуры в программировании на C?
Как здесь было отмечено, лучшим способом достижения этого в 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
}
Один случай, когда пространства имен могут быть не так полезны для достижения «статических классов», - это использование этих классов для достижения композиции вместо наследования. Пространства имен не могут быть друзьями классов и поэтому не могут получить доступ к закрытым членам класса.
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;
}
};
Одна (из многих) альтернативных, но наиболее (на мой взгляд) элегантных (по сравнению с использованием пространств имен и частных конструкторов для имитации статического поведения), способ добиться поведения «класса, который не может быть создан» в 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
class A final {
~A() = delete;
static bool your_func();
}
final
означает, что класс не может быть унаследован от.
delete
для деструктора означает, что вы не можете создать экземпляр такого класса.
Этот шаблон также известен как класс "util".
Как многие говорят, в C++ не существует концепции static class
.
Канонический namespace
, содержащий функции static
, предпочтителен в качестве решения в этом случае.
@Vagrant функция внутри пространства имен по-прежнему остается функцией. Функция, принадлежащая классу, называется методом. Если это статический метод, вы вызываете его так же, как если бы это была функция внутри пространства имен.