Как объявить статический const char * в файле заголовка?
Я хотел бы определить константу char * в моем файле заголовка для моего .cpp файла для использования. Поэтому я пробовал это:
private:
static const char *SOMETHING = "sommething";
Что вызывает меня со следующей ошибкой компилятора:
ошибка C2864: "SomeClass:: SOMETHING": только статические константные интегральные данные члены могут быть инициализированы в пределах класс
Я новичок в С++. Что здесь происходит? Почему это незаконно? И как вы можете это сделать в качестве альтернативы?
Ответы
Ответ 1
Вам нужно определить статические переменные в единицах перевода, если они не являются интегральными типами.
В заголовке:
private:
static const char *SOMETHING;
static const int MyInt = 8; // would be ok
В файле .cpp:
const char *YourClass::SOMETHING = "something";
Стандарт С++, 9.4.2/4:
Если статический член данных имеет const интегральный или const-тип перечисления, его объявление в классе определение может указывать константный инициализатор, который должен быть интегральное постоянное выражение. В этом случае, член может появиться в интегральные постоянные выражения внутри его объем. Член все равно должен быть определяется в области пространства имен, если она используемые в программе и пространстве имен определение области не должно содержать инициализатор.
Ответ 2
Чтобы ответить на вопрос о том, почему это разрешено только с интегральными типами.
Когда объект используется как lvalue (то есть как что-то, что имеет адрес в хранилище), он должен удовлетворять "одному правилу определения" (ODR), то есть он должен быть определен в одной и только одной единицы перевода. Компилятор не может и не будет решать, какую единицу перевода указать для этого объекта. Это ваша ответственность. Определяя этот объект где-то, вы не просто определяете его, вы на самом деле говорите компилятору, что хотите его определить здесь, в этой конкретной единицы перевода.
Между тем, на языке С++ интегральные константы имеют особый статус. Они могут формировать интегральные постоянные выражения (ICE). В ICE интегральные константы используются как обычные значения, а не как объекты (т.е. Не имеет значения, имеет ли такое целочисленное значение адрес в хранилище или нет). Фактически, ICE оцениваются во время компиляции. Чтобы облегчить такое использование интегральных констант, их значения должны быть видны глобально. И сама константа действительно не нуждается в фактическом месте в хранилище. Из-за этого интегральные константы получили специальное обращение: в заголовочный файл было разрешено включать их инициализаторы, и требование о предоставлении определения было ослаблено (сначала де-факто, затем де-юре).
Другие константные типы не имеют таких свойств. Другие постоянные типы практически всегда используются как lvalues (или, по крайней мере, не могут участвовать в ICE или что-то подобное ICE), что означает, что они требуют определения. Остальное следует.
Ответ 3
Ошибка заключается в том, что вы не можете инициализировать static const char*
внутри класса. Вы можете инициализировать здесь целые переменные.
Вам нужно объявить переменную-член в классе, а затем инициализировать ее вне класса:
//заголовочный файл
class Foo {
static const char *SOMETHING;
// rest of class
};
//файл cpp
const char *Foo::SOMETHING = "sommething";
Если это кажется раздражающим, подумайте об этом как о том, что инициализация может появиться только в одной единицы перевода. Если это было в определении класса, это обычно включалось бы несколькими файлами. Константные целые числа являются особым случаем (это означает, что сообщение об ошибке, возможно, не так ясно, как могло бы быть), а компиляторы могут эффективно заменить использование переменной целым значением.
Напротив, переменная char*
указывает на фактический объект в памяти, который требуется действительно существовать, и это определение (включая инициализацию), которое делает объект существующим. "Одно правило определения" означает, что вы не хотите помещать его в заголовок, потому что тогда все единицы перевода, включая этот заголовок, будут содержать определение. Они не могут быть связаны друг с другом, хотя строка содержит одни и те же символы в обоих, потому что в текущих правилах С++ вы определили два разных объекта с тем же именем, и это не является законным. Тот факт, что они имеют одинаковые символы в них, не делает его законным.
Ответ 4
class A{
public:
static const char* SOMETHING() { return "something"; }
};
Я делаю это все время - особенно для дорогих стандартных параметров по умолчанию.
class A{
static
const expensive_to_construct&
default_expensive_to_construct(){
static const expensive_to_construct xp2c(whatever is needed);
return xp2c;
}
};
Ответ 5
С С++ 11 вы можете использовать ключевое слово constexpr
и записать в свой заголовок:
private:
static constexpr const char* SOMETHING = "something";
Примечания:
-
constexpr
делает SOMETHING
постоянным указателем, поэтому вы не можете писать
SOMETHING = "something different";
позже.
-
В зависимости от вашего компилятора вам также может потребоваться написать явное определение в файле .cpp:
constexpr const char* MyClass::SOMETHING;
Ответ 6
Если вы используете Visual С++, вы можете не переносить это, используя подсказки для компоновщика...
// In foo.h...
class Foo
{
public:
static const char *Bar;
};
// Still in foo.h; doesn't need to be in a .cpp file...
__declspec(selectany)
const char *Foo::Bar = "Blah";
__declspec(selectany)
означает, что даже если Foo::Bar
будет объявлен в нескольких объектных файлах, компоновщик получит только один.
Помните, что это будет работать только с инструментальной цепочкой Microsoft. Не ожидайте, что это будет переносимым.
Ответ 7
Есть трюк, который вы можете использовать с шаблонами, чтобы предоставить только H-константы.
(заметьте, это уродливый пример, но работает дословно, по крайней мере, в g++ 4.6.1.)
(файл values.hpp)
#include <string>
template<int dummy>
class tValues
{
public:
static const char* myValue;
};
template <int dummy> const char* tValues<dummy>::myValue = "This is a value";
typedef tValues<0> Values;
std::string otherCompUnit(); // test from other compilation unit
(main.cpp)
#include <iostream>
#include "values.hpp"
int main()
{
std::cout << "from main: " << Values::myValue << std::endl;
std::cout << "from other: " << otherCompUnit() << std::endl;
}
(other.cpp)
#include "values.hpp"
std::string otherCompUnit () {
return std::string(Values::myValue);
}
Скомпилируйте (например, g++ -o main main.cpp other.cpp &./main) и посмотрите два блока компиляции, ссылающихся на одну и ту же константу, объявленную в заголовке:
from main: This is a value
from other: This is a value
В MSVC вы можете вместо этого использовать __declspec (selectany)
Например:
__declspec(selectany) const char* data = "My data";
Ответ 8
Константный инициализатор разрешен стандартом С++ только для интегральных или перечисляемых типов. Подробнее см. 9.4.2/4:
Если статический член данных имеет тип const const или const, его объявление в классе определение может указывать константный инициализатор, который должен быть интегральным постоянным выражением (5.19). В этом случай, член может появиться в интегральных постоянных выражениях. Член все еще должен быть определен в имени- объем пространства, если он используется в программе, и определение области пространства имен не должно содержать инициализатор.
И 9.4.2/7:
Элементы статических данных инициализируются и уничтожаются точно так же, как нелокальные объекты (3.6.2, 3.6.3).
Итак, вы должны написать где-нибудь в файле cpp:
const char* SomeClass::SOMETHING = "sommething";
Ответ 9
Чтобы ответить на вопрос "почему", интегральные типы являются особенными, поскольку они не относятся к выделенному объекту, а скорее к значениям, которые дублируются и копируются. Это просто решение о внедрении, когда был определен язык, который должен был обрабатывать значения вне объектной системы и как можно более эффективные и "встроенные".
Это не совсем объясняет, почему они разрешены в качестве инициализаторов в типе, но думают об этом как по существу #define
, и тогда это будет иметь смысл как часть типа, а не часть объекта.