Подтвердить что ты не робот

Как инициализировать статические элементы в заголовке

Предоставлен класс со статическим членом.

class BaseClass
{
public:
    static std::string bstring;
};

Строка, очевидно, должна быть инициализирована по умолчанию вне класса.

std::string BaseClass::bstring {"."};

Если я включаю указанную выше строку в заголовок вместе с классом, я получаю ошибку symbol multiply defined. Он должен быть определен в отдельном файле cpp, даже с include guards или pragma once.

Нет ли способа определить его в заголовке?

4b9b3361

Ответ 1

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

Однако вы можете определить функции-члены static! Теперь, на первый взгляд, может показаться, что это может не помочь, если, конечно, эта функция может иметь локальную переменную static и возвращать ссылку на одну из них ведет себя почти как переменная-член static:

static std::string& bstring() { static std::string rc{"."}; return rc; }

Локальная переменная static будет инициализирована при первом вызове этой функции. То есть, конструкция задерживается до тех пор, пока функция не будет доступна в первый раз. Конечно, если вы используете эту функцию для инициализации других глобальных объектов, она также может обеспечить, чтобы объект был построен во времени. Если вы используете несколько потоков, это может выглядеть как потенциальная гонка данных, но это не так (если вы не используете С++ 03): инициализация локальной переменной static является потокобезопасной.

Ответ 2

Относительно

" Не существует способа определить [статический член данных] в заголовке?

Да, есть.

template< class Dummy >
struct BaseClass_statics
{
    static std::string bstring;
};

template< class Dummy >
std::string BaseClass_statics<Dummy>::bstring = ".";

class BaseClass
    : public BaseClass_statics<void>
{};

Альтернативой является использование функции, как предложил Дитмар. По сути, это одноэлемент Мейерса (google it).

Изменить: также, поскольку этот ответ был опубликован, у нас есть предложение встроенного объекта, которое, как я думаю, принято для С++ 17.

В любом случае подумайте дважды о дизайне здесь. Переменные Globals - это Evil & trade;. Это по существу глобальное.

Ответ 3

Нет, это невозможно сделать в заголовке - по крайней мере, если заголовок не будет включен более одного раза в исходные файлы, что похоже на это, или вы не получите такую ​​ошибку. Просто вставьте его в один из .cpp файлов и сделайте с ним.

Ответ 4

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

class BaseClass
{
public:
  static struct _Static {
     std::string bstring {"."};
  } global;
};

Вместо инициализации отдельных элементов инициализируется вся статическая структура:

BaseClass::_Static BaseClass::global;

Доступ к значениям осуществляется с помощью

BaseClass::global.bstring;

Заметим, что это решение все еще страдает от проблемы порядка инициализация статических переменных. Когда статическое значение используется для инициализировать другую статическую переменную, первая не может быть инициализирована, пока.

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

Ответ 5

ОБНОВЛЕНИЕ: Мой ответ ниже объясняет, почему это невозможно сделать в том смысле, который был предложен вопросом. По крайней мере, два ответа обходятся; они могут или не могут решить проблему.


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

Ответ 6

§3.2.6, а следующие абзацы из текущего проекта С++ 17 (n4296) определяют правила, когда в разных единицах перевода могут присутствовать более одного определения:

Может быть более одного определения типа класса (раздел 9), типа перечисления (7.2), встроенной функции с внешняя связь (7.1.2), шаблон класса (раздел 14), шаблон нестатической функции (14.5.6), элемент статических данных шаблона класса (14.5.1.3), функции-члена шаблона класса (14.5.1.1) или специализации шаблона для которые некоторые параметры шаблона не указаны (14.7, 14.5.5) в программе, при условии, что каждое определение появляется в другой единицы перевода и при условии, что определения удовлетворяют следующим требованиям. Данный такой объект с именем D, определяемый более чем одной единицей перевода, затем [...]

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

Предлагаемые ответы от Cheers и hth. - Альф и Дитмар более похожи на "взломать", используя эти определения

элемент статических данных шаблона класса (14.5.1.3)

и

встроенная функция с внешней связью (7.1.2)

разрешены в нескольких TU (FYI: статические функции, определенные внутри определения класса, имеют внешнюю связь и неявно определяются как встроенные).

Ответ 7

Если инициализатор может быть выражен как литерал, он решается в С++ 11:

inline std::string operator"" _s(const char* p, size_t n) {
    return std::string{p, n};
}

class BaseClass {
    // inline initialization using user-defined literals
    // should allow for multiple definitions
    std::string bstring{"."_s};
};