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


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/…
bleater

Приватна статична рядок на вашій фабриці не є гарним рішенням - врахуйте, що ваші заводські клієнти повинні знати, які фігури підтримуються, тому замість того, щоб зберігати її в приватній статиці, розмістіть їх в окремому просторі імен як static 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 *. (менше накладних витрат)
KSchmidt

50
Я не впевнений, що це завжди менше накладних витрат - це залежить від використання. Якщо цей член повинен бути переданий як аргумент функціям, які приймають рядок const &, під час ініціалізації буде створено тимчасове для кожного виклику та одного створення об'єкта рядка. Накладні витрати IMHO для створення об’єкта статичного рядка незначні.
Тадеуш Копец

23
Я вважаю за краще весь час використовувати std :: string. Накладні витрати незначні, але у вас є набагато більше варіантів, і набагато менше шансів написати якісь дурні речі на кшталт "магія" == A :: ПРАВИЛЬНИЙ лише для порівняння їх адреси ...
Матьє М. Окт.

9
char const*є добро , що вона инициализируется перед усіма ініціалізації динамічної робиться. Тож у будь-якому конструкторі об'єкта ви можете розраховувати на те, RECTANGLEщо тоді вже було ініціалізовано.
Йоханнес Шауб - ліб

8
@cirosantilli: Тому що з початку часів у C ++ ініціалізатори були частинами визначень , а не деклараціями . І декларація члена члена всередині класу - це саме те, що: декларація. (З іншого боку, було зроблено виняток для інтегралів const та enum членів, а в C ++ 11 - для членів const буквальних типів.)
AnT

153

На мові C ++ 11 ви можете зараз:

class A {
 private:
  static constexpr const char* STRING = "some useful string constant";
};

30
На жаль, це рішення не працює для std :: string.
HelloWorld

2
Зауважте, що 1. це працює лише з літералами, і 2. це не відповідає стандарту, хоча Gnu / GCC відповідає штрафам, інші компілятори видадуть помилку. Визначення має бути в тілі.
ManuelSchneid3r

2
@ ManuelSchneid3r Як саме це "не відповідає стандарту"? Це виглядає як стандартна C ++ 11 дужка або однакова ініціалізація для мене.
підкреслюй_d

3
@rvighne, ні, це неправильно. constexprМається constна увазі для var, а не для його типу. Тобто static constexpr const char* constте саме що static constexpr const char*, але не те саме, що static constexpr char*.
midenok

2
@ abyss.7 - Дякую за вашу відповідь, і у мене є ще одна, будь ласка: Чому це має бути статичним?
Гай Аврахам

34

Всередині визначень класів ви можете оголошувати лише статичні члени. Їх треба визначити поза класом. Для інтегральних констант часу компіляції стандарт робить виняток, що ви можете "ініціалізувати" членів. Це все ще не визначення. Наприклад, адреса не буде працювати без визначення, наприклад.

Хочу зазначити, що я не бачу переваги використання std :: string над const char [] для констант . std :: string є приємним і всім, але він вимагає динамічної ініціалізації. Отже, якщо ви пишете щось подібне

const std::string foo = "hello";

в області простору імен конструктор foo буде запущений безпосередньо перед виконанням основних запусків, і цей конструктор створить копію постійного "привіт" у пам'яті купи. Якщо ви дійсно не потребуєте 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.

1
У C ++ 11 немає std :: string_view.
Лукаш

17

Це лише додаткова інформація, але якщо ви дійсно хочете рядок у файлі заголовка, спробуйте щось на кшталт:

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

        return str;
    }
};

Хоча я сумніваюся, що це рекомендується.


Це виглядає круто :) - Я гадаю, що ти маєш досвід інших мов, ніж c ++?
lb.

5
Я б не рекомендував це. Я роблю це часто. Це працює чудово, і я вважаю це більш очевидним, ніж введення рядка у файл реалізації. Фактичні дані std :: string все ще знаходяться на купі. Я повертаю const char *, і в цьому випадку вам не потрібно оголошувати статичну змінну, щоб декларація займала менше місця (код мудрий). Просто питання смаку.
Zoomulator

15

У C ++ 17 ви можете використовувати вбудовані змінні :

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

Зауважте, що це відрізняється від відповіді безодні.7 : Цей визначає фактичний std::stringоб'єкт, а не aconst char*


Ви не думаєте, що використання inlineстворить багато дублікатів?
шува


8

Щоб використовувати цей синтаксис ініціалізації в класі, константа повинна бути статичним констатом інтегрального чи перелічувального типу, ініціалізованим постійним виразом.

Це обмеження. Отже, у цьому випадку вам потрібно визначити змінну поза класом. посилайтесь на answwer від @AndreyT


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;

або - оскільки члени приватні і мають на увазі використовуватись лише від 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", ".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

можна просто зробити:

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

або

#define RECTANGLE "rectangle"

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.

Btw, у відповіді вище: " статичний const std :: string RECTANGLE () const ", статичних функцій не може бути, constоскільки вони не можуть змінити стан, якщо будь-який об'єкт все одно (немає цього вказівника).


4

Швидкий перехід до 2018 року та C ++ 17.

  • не використовуйте std :: string, використовуйте std :: string_view літерали
  • будь ласка, зверніть увагу на "constexpr" нижче. Це також механізм "часу компіляції".
  • ніякий вбудований не означає повторення
  • жодні файли 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;буде робитися, хоча, звичайно, область простору імен змінна, яка по суті є глобальною змінною, рідко була б хорошою ідеєю).
менталітет втрати
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.