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