Как объявить статический const char * в вашем заголовочном файле? - PullRequest
54 голосов
/ 28 октября 2009

Я хотел бы определить постоянный символ * в моем заголовочном файле для моего файла .cpp для использования Итак, я попробовал это:

private:
    static const char *SOMETHING = "sommething";

Что приводит меня к следующей ошибке компилятора:

ошибка C2864: «SomeClass :: SOMETHING»: только статические константные интегральные данные члены могут быть инициализированы в пределах класс

Я новичок в C ++. Что здесь происходит? Почему это незаконно? И как вы можете сделать это альтернативно?

Ответы [ 9 ]

64 голосов
/ 28 октября 2009

Вам необходимо определить статические переменные в единице перевода, если они не имеют целочисленных типов.

В вашем заголовке:

private:
    static const char *SOMETHING;
    static const int MyInt = 8; // would be ok

В файле .cpp:

const char *YourClass::SOMETHING = "something";

C ++ стандарт, 9.4.2 / 4:

Если член статических данных имеет постоянную целочисленный или константный тип перечисления, его объявление в классе определение может указать инициализатор констант, который должен быть интегральное постоянное выражение. В этом случай, член может появиться в интегральные константные выражения внутри его сфера применения. Член все еще должен быть определяется в области имен пространства, если это используется в программе и пространстве имен определение объема не должно содержать инициализатор.

26 голосов
/ 28 октября 2009

Чтобы ответить на вопрос ОП о том, почему он допускается только с целочисленными типами.

Когда объект используется в качестве l-значения (то есть как то, что имеет адрес в хранилище), он должен удовлетворять «правилу одного определения» (ODR), т.е. он должен быть определен в одной и только одной единице перевода. Компилятор не может и не будет решать, в каком модуле перевода будет определен этот объект. Это ваша ответственность. определяя этот объект где-то, вы не просто определяете его, вы фактически указываете компилятору, что вы хотите его определить здесь , в этой конкретной единице перевода.

Между тем, в языке C ++ интегральные константы имеют особый статус. Они могут формировать интегральные константные выражения (ICE). В ICE интегральные константы используются как обычные значения , а не как объекты (т.е. не имеет значения, имеет ли такое интегральное значение адрес в хранилище или нет). На самом деле, ICE оцениваются во время компиляции. Чтобы облегчить такое использование интегральных констант, их значения должны быть видны во всем мире. И сама константа не нуждается в реальном месте в хранилище. Из-за этого интегральные константы получили специальную обработку: было разрешено включать их инициализаторы в заголовочный файл, и требование предоставить определение было смягчено (сначала де-факто, а затем де-юре).

Другие типы констант не имеют таких свойств. Другие постоянные типы практически всегда используются в качестве l-значений (или, по крайней мере, не могут участвовать в ICE или в чем-либо подобном ICE), что означает, что они требуют определения. Остальное следует.

16 голосов
/ 28 октября 2009

Ошибка в том, что вы не можете инициализировать static const char* в классе. Там вы можете только инициализировать целочисленные переменные.

Вам необходимо объявить переменную-член в классе, а затем инициализировать ее вне класса:

// заголовочный файл

class Foo {
    static const char *SOMETHING;
    // rest of class
};

// cpp file

const char *Foo::SOMETHING = "sommething";

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

Напротив, переменная char* указывает на реальный объект в памяти, который необходим для реального существования, и именно определение (включая инициализацию) делает этот объект существующим. «Правило одного определения» означает, что вы не хотите помещать его в заголовок, потому что тогда все единицы перевода, включая этот заголовок, будут содержать определение. Они не могут быть связаны вместе, даже если строка содержит одинаковые символы в обоих, потому что в соответствии с текущими правилами C ++ вы определили два разных объекта с одним и тем же именем, и это недопустимо. Тот факт, что они содержат одинаковые символы, не делает его законным.

13 голосов
/ 15 августа 2014

С C ++ 11 вы можете использовать ключевое слово constexpr и написать в заголовке:

private:
    static constexpr const char* SOMETHING = "something";


Примечания:

  • constexpr делает SOMETHING постоянным указателем, поэтому вы не можете писать

    SOMETHING = "something different";
    

    позже.

  • В зависимости от вашего компилятора, вам также может потребоваться записать явное определение в файле .cpp:

    constexpr const char* MyClass::SOMETHING;
    
9 голосов
/ 29 октября 2009
class A{
public:
   static const char* SOMETHING() { return "something"; }
};

Я делаю это все время - особенно для дорогих параметров const по умолчанию.

class A{
   static
   const expensive_to_construct&
   default_expensive_to_construct(){
      static const expensive_to_construct xp2c(whatever is needed);
      return xp2c;
   }
};
3 голосов
/ 28 октября 2009

Существует один прием, который можно использовать с шаблонами для предоставления только констант файла 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";
3 голосов
/ 28 октября 2009

Если вы используете Visual C ++, вы можете сделать это без переноса, используя подсказки для компоновщика ...

// 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. Не ожидайте, что это будет переносимым.

1 голос
/ 28 октября 2009

Инициализатор констант допускается стандартом C ++ только для целочисленных или перечислимых типов. Подробнее см. 9.4.2 / 4:

Если статический член данных имеет константный интеграл или константный тип перечисления, его объявление в классе В определении можно указать инициализатор константы, который должен быть выражением интегральной константы (5.19). В этом В этом случае член может появляться в виде целочисленных константных выражений. Член по-прежнему должен быть определен в область действия, если она используется в программе и определение области действия пространства имен не должно содержать инициализатор.

А, 9.4.2 / 7:

Статические члены данных инициализируются и уничтожаются точно так же, как нелокальные объекты (3.6.2, 3.6.3).

Так что вы должны написать где-нибудь в файле cpp:

const char* SomeClass::SOMETHING = "sommething";
0 голосов
/ 28 октября 2009

Чтобы ответить на вопрос почему , целочисленные типы являются особенными в том смысле, что они являются не ссылкой на выделенный объект, а скорее значениями, которые дублируются и копируются. Это всего лишь решение о реализации, принятое при определении языка, которое должно было обрабатывать значения вне объектной системы и как можно более эффективно и «встроенно».

Это не совсем объясняет, почему они допускаются как инициализаторы в типе, но воспринимает это как по существу #define, и тогда это будет иметь смысл как часть типа, а не часть объекта.

...