Статическая константа (член класса)
Я хотел бы иметь частную статическую константу для класса (в данном случае фабрика форм).
Я хотел бы иметь что-то в этом роде.
class A {
private:
static const string RECTANGLE = "rectangle";
}
К сожалению, я получаю всевозможные ошибки от компилятора C++ (g++), такие как:
ISO C++ запрещает инициализацию члена 'RECTANGLE'
недопустимая инициализация в классе статического члена данных нецелого типа 'std::string'
ошибка: сделать статический RECTANGLE
Это говорит мне о том, что подобный дизайн элементов не соответствует стандарту. Как у вас есть личная литеральная константа (или, возможно, общедоступная) без необходимости использования директивы #define (я хочу избежать уродливости глобальности данных!)
Любая помощь приветствуется.
11 ответов
Вы должны определить свой статический член вне определения класса и предоставить там инициализатор.
Первый
// In a header file (if it is in a header file in your case)
class A {
private:
static const string RECTANGLE;
};
а потом
// In one of the implementation files
const string A::RECTANGLE = "rectangle";
Синтаксис, который вы изначально пытались использовать (инициализатор внутри определения класса), разрешен только для целочисленных и перечислимых типов.
Начиная с C++17 у вас есть еще один вариант, который очень похож на ваше первоначальное объявление: встроенные переменные
// In a header file (if it is in a header file in your case)
class A {
private:
inline static const string RECTANGLE = "rectangle";
};
Никакого дополнительного определения не требуется.
В C++11 вы можете сделать сейчас:
class A {
private:
static constexpr const char* STRING = "some useful string constant";
};
Внутри определений классов вы можете объявлять только статические члены. Они должны быть определены вне класса. Для интегральных констант времени компиляции стандарт делает исключение, что вы можете "инициализировать" элементы. Это все еще не определение, хотя. Взятие адреса не будет работать без определения, например.
Я хотел бы отметить, что я не вижу преимущества использования std::string перед const char[] для констант. std::string хорош и все, но требует динамической инициализации. Итак, если вы напишите что-то вроде
const std::string foo = "hello";
в области имен пространства конструктор foo будет запущен непосредственно перед выполнением основных запусков, и этот конструктор создаст копию постоянной "hello" в памяти кучи. Если вам действительно не нужен RECTANGLE, чтобы быть std::string, вы можете написать
// class definition with incomplete static member could be in a header file
class A {
static const char RECTANGLE[];
};
// this needs to be placed in a single translation unit only
const char A::RECTANGLE[] = "rectangle";
Там! Нет выделения кучи, нет копирования, нет динамической инициализации.
Ура, с.
В C++ 17 вы можете использовать встроенные переменные:
class A {
private:
static inline const std::string my_string = "some useful string constant";
};
Обратите внимание, что это отличается от ответа abyss.7: этот определяет фактический std::string
объект, а не const char*
Это просто дополнительная информация, но если вы действительно хотите строку в заголовочном файле, попробуйте что-то вроде:
class foo
{
public:
static const std::string& RECTANGLE(void)
{
static const std::string str = "rectangle";
return str;
}
};
Хотя я сомневаюсь, что это рекомендуется.
Статические переменные класса могут быть объявлены в заголовке, но должны быть определены в файле.cpp. Это связано с тем, что может быть только один экземпляр статической переменной, и компилятор не может решить, в какой сгенерированный объектный файл поместить его, поэтому вам придется принять решение.
Чтобы сохранить определение статического значения с объявлением в C++11, можно использовать вложенную статическую структуру. В этом случае статический член является структурой и должен быть определен в файле.cpp, но значения находятся в заголовке.
class A
{
private:
static struct _Shapes {
const std::string RECTANGLE {"rectangle"};
const std::string CIRCLE {"circle"};
} shape;
};
Вместо инициализации отдельных элементов вся статическая структура инициализируется в.cpp:
A::_Shapes A::shape;
Доступ к значениям
A::shape.RECTANGLE;
или - поскольку члены являются частными и предназначены для использования только из А - с
shape.RECTANGLE;
Обратите внимание, что это решение все еще страдает от проблемы порядка инициализации статических переменных. Когда статическое значение используется для инициализации другой статической переменной, первая может еще не инициализироваться.
// file.h
class File {
public:
static struct _Extensions {
const std::string h{ ".h" };
const std::string hpp{ ".hpp" };
const std::string c{ ".c" };
const std::string cpp{ ".cpp" };
} extension;
};
// file.cpp
File::_Extensions File::extension;
// module.cpp
static std::set<std::string> headers{ File::extension.h, File::extension.hpp };
В этом случае заголовки статических переменных будут содержать либо { "" }, либо { ".h", ".hpp" }, в зависимости от порядка инициализации, созданного компоновщиком.
Как уже упоминалось @abyss.7, вы также можете использовать constexpr
если значение переменной может быть вычислено во время компиляции. Но если вы объявите свои строки с static constexpr const char*
и ваша программа использует std::string
в противном случае будут накладные расходы, потому что новый std::string
Объект будет создаваться каждый раз, когда вы используете такую константу:
class A {
public:
static constexpr const char* STRING = "some value";
};
void foo(const std::string& bar);
int main() {
foo(A::STRING); // a new std::string is constructed and destroyed.
}
Чтобы использовать этот синтаксис инициализации в классе, константа должна быть статической константой целочисленного типа или типа перечисления, инициализированной константным выражением.
Это ограничение. Следовательно, в этом случае вам нужно определить переменную вне класса. см. ответ от @AndreyT
Вы можете пойти на const char*
Решение, упомянутое выше, но тогда, если вам нужно все время строку, у вас будет много накладных расходов.
С другой стороны, статическая строка требует динамической инициализации, поэтому, если вы хотите использовать ее значение во время инициализации другой глобальной / статической переменной, вы можете столкнуться с проблемой порядка инициализации. Чтобы избежать этого, самый дешевый способ - получить доступ к статическому строковому объекту через геттер, который проверяет, инициализирован ли ваш объект или нет.
//in a header
class A{
static string s;
public:
static string getS();
};
//in implementation
string A::s;
namespace{
bool init_A_s(){
A::s = string("foo");
return true;
}
bool A_s_initialized = init_A_s();
}
string A::getS(){
if (!A_s_initialized)
A_s_initialized = init_A_s();
return s;
}
Не забудьте использовать только A::getS()
, Потому что любой поток может быть запущен только main()
, а также A_s_initialized
инициализируется раньше main()
вам не нужны блокировки даже в многопоточной среде. A_s_initialized
по умолчанию 0 (до динамической инициализации), поэтому, если вы используете getS()
перед инициализацией s вы вызываете функцию init безопасно.
Кстати, в ответе выше: "static const std:: string RECTANGLE () const", статические функции не могут быть const
потому что они не могут изменить состояние, если любой объект в любом случае (этот указатель отсутствует).
Перенесемся в 2018 и C++17.
using namespace std::literals;
namespace STANDARD {
constexpr
inline
auto
compiletime_static_string_view_constant() {
// make and return string view literal
// will stay the same for the whole application lifetime
// will exhibit standard and expected interface
// will be usable at both
// runtime and compile time
// by value semantics implemented for you
auto made_once_when_needed_ = "compile time"sv;
return made_once_when_needed_ ;
}
};
Выше - надлежащий и юридический стандарт гражданина C++. Он может быть легко вовлечен в любые алгоритмы std::, контейнеры, утилиты и тому подобное. Например:
// test the resilience
auto return_by_val = []() {
auto return_by_val = []() {
auto return_by_val = []() {
auto return_by_val = []() {
return STANDARD::compiletime_static_string_view_constant();
};
return return_by_val();
};
return return_by_val();
};
return return_by_val();
};
// actually a run time
_ASSERTE(return_by_val() == "compile time");
// compile time
static_assert(
STANDARD::compiletime_static_string_view_constant()
== "compile time"
);
Наслаждайтесь стандартом C++
Можно просто сделать:
static const std::string RECTANGLE() const {
return "rectangle";
}
или же
#define RECTANGLE "rectangle"
Текущий стандарт допускает такую инициализацию только для статических постоянных целочисленных типов. Так что вам нужно сделать, как объяснил AndreyT. Однако это будет доступно в следующем стандарте через новый синтаксис инициализации члена.