Стандарт C ++ гарантирует создание статических локальных переменных при первом использовании. Однако мне интересно, что произойдет, если я получу доступ к статическому локальному объекту во время его создания. Я предполагаю, что это УБ. Но как лучше всего избежать этого в следующей ситуации?
Проблемная ситуация
Шаблон Meyers Singleton использует статический локальный объект в статическом методе getInstance()
для создания объекта при первом использовании. Теперь, если конструктор (напрямую или косвенно) снова вызовет getInstance()
, мы столкнемся с ситуацией, когда статическая инициализация еще не завершена. Вот минимальный пример, иллюстрирующий проблемную ситуацию:
class StaticLocal {
private:
StaticLocal() {
// Indirectly calls getInstance()
parseConfig();
}
StaticLocal(const StaticLocal&) = delete;
StaticLocal &operator=(const StaticLocal &) = delete;
void parseConfig() {
int d = StaticLocal::getInstance()->getData();
}
int getData() {
return 1;
}
public:
static StaticLocal *getInstance() {
static StaticLocal inst_;
return &inst_;
}
void doIt() {};
};
int main()
{
StaticLocal::getInstance()->doIt();
return 0;
}
В VS2010 это работает без проблем, но VS2015 заходит в тупик.
Для этой простой упрощенной ситуации очевидным решением является прямой вызов getData()
без повторного вызова getInstance()
. Однако в более сложных сценариях (как в моей реальной ситуации) это решение невозможно.
Попытка решения
Если мы изменим метод getInstance()
для работы со статическим локальным указателем, подобным этому (и, таким образом, откажемся от шаблона Meyers Singleton):
static StaticLocal *getInstance() {
static StaticLocal *inst_ = nullptr;
if (!inst_) inst_ = new StaticLocal;
return inst_;
}
Понятно, что мы получаем бесконечную рекурсию. inst_
- это nullptr
при первом вызове, поэтому мы вызываем конструктор с new StaticLocal
. На этом этапе inst_
по-прежнему nullptr
, поскольку он будет назначен только после завершения работы конструктора. Однако конструктор снова вызовет getInstance()
, найдет nullptr
в inst_
и, таким образом, снова вызовет конструктор. И снова, и снова ...
Возможное решение - переместить тело конструктора в getInstance()
:
StaticLocal() { /* do nothing */ }
static StaticLocal *getInstance() {
static StaticLocal *inst_ = nullptr;
if (!inst_) {
inst_ = new StaticLocal;
inst_->parseConfig();
}
return inst_;
}
Это сработает. Однако меня не устраивает такая ситуация, поскольку конструктор должен конструировать законченный объект. Можно спорить, можно ли сделать эту ситуацию исключением, поскольку это синглтон. Однако мне это не нравится.
Но более того, что, если в классе есть нетривиальный деструктор?
~StaticLocal() { /* Important Cleanup */ }
В приведенной выше ситуации деструктор никогда не вызывается. Мы теряем RAII и, следовательно, одну важную отличительную черту C ++! Мы живем в мире Java или C # ...
Итак, мы могли бы обернуть наш синглтон в какой-то умный указатель:
static StaticLocal *getInstance() {
static std::unique_ptr<StaticLocal> inst_;
if (!inst_) {
inst_.reset(new StaticLocal);
inst_->parseConfig();
}
return inst_.get();
}
Это правильно вызовет деструктор при выходе из программы. Но это заставляет нас сделать деструктор общедоступным.
На данный момент я чувствую, что делаю работу компилятора ...
Вернуться к исходному вопросу
Это действительно неопределенное поведение? Или это ошибка компилятора в VS2015?
Как лучше всего выходить из такой ситуации, желательно не отбрасывая полный конструктор и RAII?
int d = getData();
. - person Marian Spanik   schedule 09.03.2016parseConfig
, вызываютgetData
(и другие методы). Я мог бы переписать код, чтобы передатьStaticLocal
указатель / ссылку на конструкторы этих классов, но это было бы довольно переделкой ... - person king_nak   schedule 09.03.2016