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

385

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

Я бы хотел кое-что сделать.

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

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

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

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

ошибка: создание "СТАНДАРТНАЯ СТАНЦИЯ RECTANGLE

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

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

  • 12
    Спасибо за все ваши отличные ответы! Да здравствует ТАК!
  • 0
    Может кто-нибудь сказать мне, что такое «интегральный» тип? Большое спасибо.
Показать ещё 3 комментария
Теги:
class
string
static
const

11 ответов

429
Лучший ответ

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

Первая

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

Синтаксис, который вы изначально пытались использовать (определение инициализатора внутри класса) допускается только с интегральными и перечисляемыми типами.

  • 7
    Кроме того, если нет необходимости использовать строку STL, вы также можете просто определить const char *. (меньше накладных расходов)
  • 47
    Я не уверен, что это всегда меньше накладных расходов - это зависит от использования. Если этот член предназначен для передачи в качестве аргумента функциям, которые принимают константную строку &, во время инициализации для каждого вызова будет создано временное создание против одного строкового объекта. Затраты IMHO на создание статического строкового объекта ничтожны.
Показать ещё 11 комментариев
139

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

class A {
 private:
  static constexpr const char* STRING = "some useful string constant";
};
  • 0
    Спасибо, что спасли день. Мне нужно было немного размера статического массива в моем классе. Невозможно без constexpr (для массива), если вы не хотите выделять кучу каждый раз, когда создается класс.
  • 26
    К сожалению, это решение не работает для std :: string.
Показать ещё 8 комментариев
30

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

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

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

Приветствия, с.

  • 1
    Это предварительный ответ C ++ 11. Используйте стандартный C ++ и используйте std :: string_view.
15

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

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

        return str;
    }
};

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

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

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

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

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

  • 0
    Не думаете ли вы, что использование inline создаст много дубликатов?
  • 1
    @shuva Нет, переменная не будет дублироваться .
Показать ещё 1 комментарий
7

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

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

4

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

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

или

#define RECTANGLE "rectangle"
  • 11
    Использование #define, когда можно использовать типизированную константу, просто неправильно.
  • 0
    Ваш первый пример в основном является хорошим решением, если у вас нет constexpr но вы не можете сделать статическую функцию const .
Показать ещё 2 комментария
4

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

3

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

Сохранение определения статического значения с помощью объявления в С++ 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;

или - поскольку члены являются частными и предназначены для использования только с A - с

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", ".pp" }, в зависимости от порядка инициализации, созданного компоновщиком.

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

Вы можете либо пойти для решения const char*, упомянутого выше, но затем, если вам нужна строка все время, у вас будет много накладных расходов.
С другой стороны, статическая строка требует динамической инициализации, поэтому, если вы хотите использовать ее значение во время другой инициализации глобальной/статической переменной, вы можете столкнуться с проблемой порядка инициализации. Чтобы этого избежать, самым дешевым является доступ к статическому объекту string через getter, который проверяет, инициализирован ли ваш объект или нет.

//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 безопасно.

Btw в ответе выше: "static const std::string RECTANGLE() const", статические функции не могут быть const, потому что они не могут изменять состояние, если какой-либо объект в любом случае (этот указатель отсутствует).

1

Ускорьтесь до 2018 года и C++ 17.

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

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++

Ещё вопросы

Сообщество Overcoder
Наверх
Меню