Это сообщение может показаться слишком длинным только для короткого вопроса в конце.Но мне также нужно описать шаблон дизайна, который я только что придумал.Может быть, он обычно используется, но я никогда не видел его (или, может быть, он просто не работает:).
Во-первых, вот код, который (на мой взгляд) имеет неопределенное поведение из-за «статического порядка инициализации».фиаско".Проблема в том, что инициализация 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();
И вот вопрос:Возможно ли, что некоторый компилятор может оптимизировать вызовы функций (в данном случае конструкторов), которые имеют локальные статические данные?Таким образом, вопрос в том, что именно означает «иметь побочные эффекты», что, насколько я понимаю, означает, что компилятору не разрешено его оптимизировать.Достаточно ли наличия локальных статических данных для функции, чтобы заставить компилятор думать, что вызов функции нельзя игнорировать?