Упорядоченная статическая инициализация потоковых классов - PullRequest
2 голосов
/ 11 января 2012

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

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

Файл: English.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;
}());

Файл: Spanish.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 и делаем эти статические инициализации функциональными локальными, например:

Файл: English.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];
    }
};

Файл: Spanish.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];
    }
};

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

Файл: English.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();

Файл: Spanish.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();

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

Ответы [ 4 ]

1 голос
/ 19 января 2012

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

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

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

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

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

Кроме того, в 3.7.2 «Длительность автоматического хранения» [basic.stc.auto] говорится, что

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

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

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

1 голос
/ 18 января 2012

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

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

1 голос
/ 11 января 2012

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

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

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

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

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

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

0 голосов
/ 17 января 2012

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

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

static staticObjects objects = new staticObjects();

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...