Упорядоченная статическая инициализация потокобезопасных классов

StackOverflow https://stackoverflow.com/questions/8822229

Вопрос

Этот пост может показаться слишком длинным из-за короткого вопроса в конце.Но мне также нужно описать шаблон дизайна, который я только что придумал.Может быть, это часто используется, но я никогда его не видел (или, может быть, это просто не работает :).

Во-первых, вот код, который (насколько я понимаю) имеет неопределенное поведение из-за "фиаско порядка статической инициализации".Проблема в том, что инициализация Spanish::s_englishToSpanish зависит от English::s_numberToStr , которые как статически инициализированы, так и находятся в разных файлах, поэтому порядок этих инициализаций не определен:

Файл:Английский.h

#pragma once

#include <vector>
#include <string>

using namespace std;

struct English {
    static vector<string>* s_numberToStr;
    string m_str;

    explicit English(int number)
    {
        m_str = (*s_numberToStr)[number];
    }
};

Файл:English.cpp

#include "English.h"

vector<string>* English::s_numberToStr = new vector<string>( /*split*/
[]() -> vector<string>
{
    vector<string> numberToStr;
    numberToStr.push_back("zero");
    numberToStr.push_back("one");
    numberToStr.push_back("two");
    return numberToStr;
}());

Файл:Испанский.h

#pragma once

#include <map>
#include <string>

#include "English.h"

using namespace std;

typedef map<string, string> MapType;

struct Spanish {
    static MapType* s_englishToSpanish;
    string m_str;

    explicit Spanish(const English& english)
    {
        m_str = (*s_englishToSpanish)[english.m_str];
    }
};

Файл:Spanish.cpp

#include "Spanish.h"

MapType* Spanish::s_englishToSpanish = new MapType( /*split*/
[]() -> MapType
{
    MapType englishToSpanish;
    englishToSpanish[ English(0).m_str ] = "cero";
    englishToSpanish[ English(1).m_str ] = "uno";
    englishToSpanish[ English(2).m_str ] = "dos";
    return englishToSpanish;
}());

Файл:StaticFiasco.h

#include <stdio.h>
#include <tchar.h>
#include <conio.h>

#include "Spanish.h"

int _tmain(int argc, _TCHAR* argv[])
{
    _cprintf( Spanish(English(1)).m_str.c_str() ); // may print "uno" or crash

    _getch();
    return 0;
}

Чтобы решить проблему порядка статической инициализации, мы используем идиому construct-on-first-use и делаем эти статические инициализации функционально-локальными примерно так:

Файл:Английский.h

#pragma once

#include <vector>
#include <string>

using namespace std;

struct English {
    string m_str;

    explicit English(int number)
    {
        static vector<string>* numberToStr = new vector<string>( /*split*/
        []() -> vector<string>
        {
            vector<string> numberToStr_;
            numberToStr_.push_back("zero");
            numberToStr_.push_back("one");
            numberToStr_.push_back("two");
            return numberToStr_;
        }());

        m_str = (*numberToStr)[number];
    }
};

Файл:Испанский.h

#pragma once

#include <map>
#include <string>

#include "English.h"

using namespace std;

struct Spanish {
    string m_str;

    explicit Spanish(const English& english)
    {
        typedef map<string, string> MapT;

        static MapT* englishToSpanish = new MapT( /*split*/
        []() -> MapT
        {
            MapT englishToSpanish_;
            englishToSpanish_[ English(0).m_str ] = "cero";
            englishToSpanish_[ English(1).m_str ] = "uno";
            englishToSpanish_[ English(2).m_str ] = "dos";
            return englishToSpanish_;
        }());

        m_str = (*englishToSpanish)[english.m_str];
    }
};

Но теперь у нас есть еще одна проблема.Из-за локальных статических данных функции ни один из этих классов не является потокобезопасным.Чтобы решить эту проблему, мы добавляем к обоим классам статическую переменную-член и функцию инициализации для нее.Затем внутри этой функции мы принудительно инициализируем все локальные статические данные функции, вызывая по одному разу каждую функцию, которая имеет локальные статические данные функции.Таким образом, фактически мы инициализируем все в начале программы, но по-прежнему контролируем порядок инициализации.Итак, теперь наши классы должны быть потокобезопасными:

Файл:Английский.h

#pragma once

#include <vector>
#include <string>

using namespace std;

struct English {
    static bool s_areStaticsInitialized;
    string m_str;

    explicit English(int number)
    {
        static vector<string>* numberToStr = new vector<string>( /*split*/
        []() -> vector<string>
        {
            vector<string> numberToStr_;
            numberToStr_.push_back("zero");
            numberToStr_.push_back("one");
            numberToStr_.push_back("two");
            return numberToStr_;
        }());

        m_str = (*numberToStr)[number];
    }

    static bool initializeStatics()
    {
        // Call every member function that has local static data in it:
        English english(0); // Could the compiler ignore this line?
        return true;
    }
};
bool English::s_areStaticsInitialized = initializeStatics();

Файл:Испанский.h

#pragma once

#include <map>
#include <string>

#include "English.h"

using namespace std;

struct Spanish {
    static bool s_areStaticsInitialized;
    string m_str;

    explicit Spanish(const English& english)
    {
        typedef map<string, string> MapT;

        static MapT* englishToSpanish = new MapT( /*split*/
        []() -> MapT
        {
            MapT englishToSpanish_;
            englishToSpanish_[ English(0).m_str ] = "cero";
            englishToSpanish_[ English(1).m_str ] = "uno";
            englishToSpanish_[ English(2).m_str ] = "dos";
            return englishToSpanish_;
        }());

        m_str = (*englishToSpanish)[english.m_str];
    }

    static bool initializeStatics()
    {
        // Call every member function that has local static data in it:
        Spanish spanish( English(0) ); // Could the compiler ignore this line?
        return true;
    }
};

bool Spanish::s_areStaticsInitialized = initializeStatics();

И вот в чем вопрос:Возможно ли, что какой-нибудь компилятор может оптимизировать те вызовы функций (в данном случае конструкторов), которые имеют локальные статические данные?Итак, вопрос в том, что именно означает "наличие побочных эффектов", что, насколько я понимаю, означает, что компилятору не разрешено оптимизировать его.Достаточно ли наличия локальных статических данных для функции, чтобы компилятор подумал, что вызов функции нельзя игнорировать?

Это было полезно?

Решение

В разделе 1.9 "Выполнение программы" [intro.execution] стандарта C ++ 11 говорится, что

1 Семантические описания в настоящем Международном стандарте определяют параметризованную недетерминированную абстрактную машину....соответствующие реализации необходимы для эмуляции (только) наблюдаемого поведения абстрактной машины, как описано ниже.
...

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

8 Наименьшими требованиями к соответствующей реализации являются:
— Доступ к изменяемым объектам оценивается строго в соответствии с правилами абстрактной машины.
— При завершении работы программы все данные, записанные в файлы, должны быть идентичны одному из возможных результатов, к которым привело бы выполнение программы в соответствии с абстрактной семантикой.
— Динамика ввода и вывода интерактивных устройств должна осуществляться таким образом, чтобы вывод запроса фактически осуществлялся до того, как программа ожидает ввода.То, что представляет собой интерактивное устройство, определяется реализацией.
В совокупности они называются наблюдаемое поведение из программы.
...

Доступ к объекту, обозначенному изменяемым значением glvalue (3.10), изменение объекта, вызов библиотечной функции ввода-вывода или вызов функции, выполняющей любую из этих операций, - все это побочные эффекты, которые представляют собой изменения в состоянии среды выполнения.

Кроме того, в разделе 3.7.2 "Автоматическая продолжительность хранения" [basic.stc.auto] сказано, что

3 Если переменная с длительностью автоматического хранения имеет инициализацию или деструктор с побочными эффектами, она не должна быть уничтожена до окончания ее блокировки и не должна быть удалена в качестве оптимизации, даже если кажется, что она не используется, за исключением того, что объект класса или его копия / перемещение могут быть удалены, как указано в 12.8.

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

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

Другие советы

Хорошо, в двух словах:

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

  2. Не делайте их частными, а вместо этого делайте их членами компиляционного блока (где будет код, который реализует ваши занятия).

  3. Использовать boost::call_once выполнить статическую инициализацию.

Инициализация при первом использовании относительно проста для обеспечения соблюдения упорядочения, это разрушение, которое гораздо сложнее выполнить в порядке. Обратите внимание, однако, что функция, используемая в Call_once, не должна бросать исключение. Поэтому, если это может потерпеть неудачу, вы должны оставить какое -то неудачное состояние и проверить это после вызова.

(Я предполагаю, что в вашем реальном примере ваша нагрузка-это не то, что вы жестко код, но, скорее всего, вы загружаете какую-то динамическую таблицу, поэтому вы не можете просто создать массив в памяти).

Почему бы вам просто не скрыть английский :: s_numbertoStr за публичной статической функцией и полностью пропустить синтаксис конструктора? Использовать DCLP Чтобы обеспечить безопасность потока.

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

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

class staticObjects
{
    private:
    vector<string>* English::s_numberToStr;
    MapType* s_englishToSpanish;
};

static staticObjects objects = new staticObjects();

а затем определите несколько интерфейсов, чтобы получить его.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top