Статическая константная строка (член класса)

445

Я хотел бы иметь частную статическую константу для класса (в данном случае фабрика форм).

Я хотел бы иметь что-то в этом роде.

class A {
   private:
      static const string RECTANGLE = "rectangle";
}

К сожалению, я получаю всевозможные ошибки от компилятора C ++ (g ++), такие как:

ISO C ++ запрещает инициализацию члена 'RECTANGLE'

недопустимая инициализация в классе статического члена данных нецелого типа 'std :: string'

ошибка: сделать статический RECTANGLE

Это говорит мне о том, что подобный дизайн элементов не соответствует стандарту. Как у вас есть личная литеральная константа (или, возможно, общедоступная) без необходимости использования директивы #define (я хочу избежать уродливости глобальности данных!)

Любая помощь приветствуется.

фунт.
источник
15
Спасибо за все ваши отличные ответы! Да здравствует ТАК!
lb.
Может кто-нибудь сказать мне, что такое «интегральный» тип? Большое спасибо.
lb.
1
Интегральные типы относятся к типам, которые представляют целые числа. См. Publib.boulder.ibm.com/infocenter/comphelp/v8v101/…
блеатер
Закрытая статическая строка в вашей фабрике не является хорошим решением - учтите, что ваши фабричные клиенты должны будут знать, какие фигуры поддерживаются, поэтому вместо того, чтобы хранить ее в закрытой статической, поместите их в отдельное пространство имен как статическое const std :: string RECTANGLE = "Rectangle ».
LukeCodeBaker
если ваш класс является классом шаблона, см. stackoverflow.com/q/3229883/52074
Тревор Бойд Смит,

Ответы:

471

Вы должны определить свой статический член вне определения класса и предоставить там инициализатор.

Первый

// 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";
};

Никакого дополнительного определения не требуется.

Или вместо этого constвы можете объявить это constexprв этом варианте. Явное inlineбольше не нужно, поскольку constexprподразумевает inline.

Муравей
источник
8
Кроме того, если нет необходимости использовать строку STL, вы также можете просто определить const char *. (меньше накладных расходов)
КШмидт
50
Я не уверен, что это всегда меньше накладных расходов - это зависит от использования. Если этот элемент предназначен для передачи в качестве аргумента функциям, которые принимают константную строку &, во время инициализации для каждого вызова будет создано временное создание против одного строкового объекта. Затраты IMHO на создание статического строкового объекта ничтожны.
Тадеуш Копец
23
Я бы предпочел использовать std :: string тоже все время. Затраты незначительны, но у вас гораздо больше вариантов, и гораздо меньше шансов написать некоторые глупые вещи, такие как "magic" == A :: RECTANGLE, только для сравнения их адреса ...
Матье М.
9
char const*есть добро , что она инициализируется перед всеми инициализации динамической делается. Таким образом, в конструкторе любого объекта вы можете положиться на то, RECTANGLEчто он уже был инициализирован.
Йоханнес Шауб -
8
@cirosantilli: потому что с самого начала в C ++ инициализаторы были частями определений , а не объявлений . И объявление члена данных внутри класса - это просто объявление. (С другой стороны, было сделано исключение для константных интегральных и перечислимых членов, а в C ++ 11 - для константных членов литеральных типов.)
AnT
153

В C ++ 11 вы можете сделать сейчас:

class A {
 private:
  static constexpr const char* STRING = "some useful string constant";
};
abyss.7
источник
31
К сожалению, это решение не работает для std :: string.
HelloWorld
2
Обратите внимание, что 1. это работает только с литералами и 2. это не соответствует стандарту, хотя Gnu / GCC компилирует штрафы, другие компиляторы будут выдавать ошибку. Определение должно быть в теле.
ManuelSchneid3r
2
@ ManuelSchneid3r Как именно это "не соответствует стандарту"? Для меня это выглядит как стандартная инициализация C ++ 11 в скобках или равная скобка .
underscore_d
3
@ rvighne, нет, это неправильно. constexprподразумевает constдля вар, а не для типа он указывает. Т.е. static constexpr const char* constтакой же, как static constexpr const char*, но не такой, как static constexpr char*.
Миденок
2
@ abyss.7 - Спасибо за ваш ответ, и у меня есть еще один, пожалуйста: почему он должен быть статичным?
Гай Авраам
34

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

Я хотел бы отметить, что я не вижу преимущества использования 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";

Там! Нет выделения кучи, нет копирования, нет динамической инициализации.

Ура, с.

sellibitze
источник
1
Это предварительный ответ C ++ 11. Используйте стандартный C ++ и используйте std :: string_view.
1
C ++ 11 не имеет std :: string_view.
Лукас Салич
17

Это просто дополнительная информация, но если вам действительно нужна строка в заголовочном файле, попробуйте что-то вроде:

class foo
{
public:
    static const std::string& RECTANGLE(void)
    {
        static const std::string str = "rectangle";

        return str;
    }
};

Хотя я сомневаюсь, что это рекомендуется.

GManNickG
источник
Это выглядит круто :) - я предполагаю, что у вас есть опыт работы на других языках, кроме с ++?
lb.
5
Я бы не рекомендовал это. Я делаю это часто. Это прекрасно работает, и я считаю это более очевидным, чем помещение строки в файл реализации. Фактические данные std :: string все еще находятся в куче. Я бы возвратил const char *, и в этом случае вам не нужно объявлять статическую переменную, чтобы объявление занимало меньше места (по кодам). Просто вопрос вкуса.
Zoomulator
15

В C ++ 17 вы можете использовать встроенные переменные :

class A {
 private:
  static inline const std::string my_string = "some useful string constant";
};

Обратите внимание, что это отличается от ответа abyss.7 : этот определяет реальный std::stringобъект, а неconst char*

Оз Соломон
источник
Не думаете ли вы, что использование inlineсоздаст много дубликатов?
Шува
8

Чтобы использовать этот синтаксис инициализации в классе, константа должна быть статической константой целочисленного типа или типа перечисления, инициализированной константным выражением.

Это ограничение. Следовательно, в этом случае вам нужно определить переменную вне класса. см. ответ от @AndreyT

Aj.
источник
7

Статические переменные класса могут быть объявлены в заголовке, но должны быть определены в файле .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.
}
Марко Махнич
источник
Хорошо подготовленный ответ Марко. Две детали: одному не нужны файлы cpp для статических членов класса, а также, пожалуйста, используйте std :: string_view для любого вида констант.
4

Текущий стандарт допускает такую ​​инициализацию только для статических постоянных целочисленных типов. Так что вам нужно сделать, как объяснил AndreyT. Однако это будет доступно в следующем стандарте через новый синтаксис инициализации члена .

Леандро ТЦ Мело
источник
4

можно просто сделать:

static const std::string RECTANGLE() const {
    return "rectangle";
} 

или

#define RECTANGLE "rectangle"
chikuba
источник
11
Использование #define, когда можно использовать типизированную константу, просто неправильно.
Артур Цайка
Ваш первый пример - хорошее решение, если у вас его нет, constexprно вы не можете создать статическую функцию const.
Фрэнк Пуффер
Этого решения следует избегать. Он создает новую строку при каждом вызове. Это было бы лучше:static const std::string RECTANGLE() const { static const std::string value("rectangle"); return value; }
Оз Соломон
Зачем использовать полноценный контейнер в качестве возвращаемого значения? Используйте std :: string_vew .. в этом случае его содержимое останется действительным. еще лучше использовать строковые литералы для создания и возврата строкового представления ... и последнее, но не по значению, возвращаемое значение const не имеет здесь никакого значения или эффекта ... да, и иметь это как встроенный, а не статический, в некотором заголовке в именованное пространство имен ... и, пожалуйста, сделайте его constexpr
4

Вы можете выбрать 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потому что они не могут изменить состояние, если какой-либо объект в любом случае (этот указатель отсутствует).

user2806882
источник
4

Перенесемся в 2018 и C ++ 17.

  • не используйте std :: string, используйте литералы std :: string_view
  • пожалуйста, обратите внимание на «constexpr» ниже. Это также механизм «времени компиляции».
  • нет inline не означает повторение
  • никакие cpp файлы для этого не нужны
  • static_assert 'работает' только во время компиляции

    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 when_needed_ =  "compile time"sv;
        return 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 ++


источник
Используйте std::string_viewдля констант, только если вы используете string_viewпараметры во всех ваших функциях. Если какая-либо из ваших функций использует const std::string&параметр, при передаче string_viewконстанты через этот параметр будет создана копия строки . Если ваши константы имеют тип, std::stringкопии не будут созданы ни для const std::string&параметров, ни для std::string_viewпараметров.
Марко Махнич
Хороший ответ, но любопытно, почему string_view возвращается из функции? Такая хитрость была полезна до того, как inlineпеременные появились в C ++ 17 с их семантикой ODR. Но string_view тоже C ++ 17, так что просто constexpr auto some_str = "compile time"sv;выполняет работу (и на самом деле, это не переменная, а constexpr, значит inline, неявная; если у вас есть переменная - то есть, нет constexpr- тогда это inline auto some_str = "compile time"sv;будет сделано, хотя, конечно, область имен пространства имен переменная, которая по сути является глобальной переменной, редко будет хорошей идеей).
Потеря Менталитета