Этот пост может показаться слишком длинным для краткого вопроса в конце его. Но мне также нужно описать шаблон дизайна, который я только что придумал. Может быть, это обычно используется, но я никогда не видел его (или, может быть, он просто не работает :).Упорядоченная статическая инициализация потокобезопасных классов
Во-первых, вот код, который (насколько мне известно) имеет неопределенное поведение из-за «статического инициализационного порядка фиаско». Проблема заключается в том, что инициализация испанского :: s_englishToSpanish зависит от английского :: 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;
}
Чтобы решить проблему статического порядка инициализации, мы используем конструкт-на-первого использования идиомы, и сделать эти статические инициализацию функция локального, так как:
Файл: 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();
И вот вопрос: Возможно ли, что некоторые компилятор может оптимизировать эти вызовы функций (конструкторы в этом случае), которые имеют локальные статические данные? Таким образом, вопрос заключается в том, что в точности означает «наличие побочных эффектов», что, на мой взгляд, означает, что компилятору не разрешено его оптимизировать. Имеет ли локально локальные статические данные достаточно, чтобы заставить компилятор думать, что вызов функции нельзя игнорировать?
Если у вас уже есть C++ 11 (как указано с помощью lambdas), почему бы не использовать инициализацию списка для векторов сразу? –
1) Компилятор Intel C++ еще не поддерживает списки инициализаторов. 2) Если мне нужно использовать функцию или загрузить из файла для инициализации данных, список инициализаторов не будет применяться. 3) Более того, список инициализаторов ничего не помог бы решить проблему статического инициализации, о которой я говорю, поэтому я не вижу, насколько они актуальны. – zeroes00