Многие программисты на C ++ пострадали от ожесточенных столкновений с инициализацией / очисткой глобальных объектов C ++.В конце концов я нашел достаточно хорошее решение этой проблемы, я использую (и радуюсь) это в течение многих лет.У меня вопрос: полностью ли это решение соответствует стандарту C ++ или «зависит от платформы / реализации»?
Проблема
Вообще говоря, есть дваОсновные проблемы с глобальными объектами:
- Непредсказуемый порядок их постройки / уничтожения.Это кусает, если эти объекты зависят друг от друга.
- Код построения / разрушения выполняется во время инициализации / очистки CRT вне основной точки входа в программу.Нет способа обернуть этот код с помощью
try
/ catch
или выполнить какую-либо предварительную инициализацию.
Один из способов преодоления этих проблем - , а не с использованием глобальных объектов вообще.,Вместо этого можно использовать статические / глобальные указатели на эти объекты.Во время инициализации программы эти объекты либо распределяются динамически, либо создаются как автоматические переменные в функции точки входа (main
), и их указатели хранятся в этих указателях.Таким образом, вы имеете полный контроль над временем жизни ваших «глобальных» объектов.
Однако этот метод также имеет некоторые недостатки.Это связано с тем, что отличается не только создание / уничтожение этих объектов, но и различается их доступ .Обычно глобальный объект находится в разделе данных, который выделяется загрузчиком, и его виртуальный адрес известен во время сборки.Использование глобальных указателей приводит к следующим недостаткам:
- Несколько более медленный доступ к объекту, дополнительная разыменование указателя.Во время выполнения вместо предположения, что объект находится по указанному адресу, компилятор генерирует код, который разыменовывает глобальный указатель.
- Более слабые оптимизации.Компилятор может не понимать, что указатель всегда указывает на один и тот же объект.
- Если фактические объекты размещены в куче:
- Хуже производительность (выделения кучи "тяжелые"")
- Вероятность исключения нехватки памяти
- Если фактические объекты размещены в стеке (автоматические переменные в
main
): - Размер стека обычно ограничен.В некоторых обстоятельствах его потребление "толстыми" объектами неоптимально.
Решение
Решение, которое я нашел, заключается впереопределить операторы new
/ delete
.
// class definition
class MyObject
{
// some members
// ...
static char s_pMyPlaceholder[];
public:
// methods
// ...
static MyObject& Instance()
{
return *(MyObject*) s_pMyPlaceholder;
}
void* operator new (size_t) { return s_pMyPlaceholder; }
void operator delete (void*) {}
};
// object placeholder instantiated
char MyObject::s_pMyPlaceholder[sizeof(MyObject)];
void main()
{
// global initialization
std::auto_ptr<MyObject> pMyObj(new MyObject);
// run the program
// ...
}
Хитрость заключается в том, чтобы выделить достаточно места в глобальной памяти (объявив глобальный массив соответствующего размера), а затем использовать фиктивное выделение памятидля необходимого объекта, который будет «распределять» эту глобальную память ». Таким образом мы достигаем следующего:
- Семантически мы выделяем объект динамически. Следовательно, мы имеем полный контроль над его временем жизни.
- На самом деле объект находится в глобальной памяти 1080 *. Следовательно, все недостатки, связанные с "указательным методом", неприменимы к нашему случаю.
- Объект виден везде вПрограмма вызывает один
MyObject::Instance()
, чтобы получить ссылку на него. И, кстати, этот вызов функции легко встроен компилятором.
Так что все выглядит нормально с этим методомд.Мне просто любопытно, законно ли это с точки зрения стандарта C ++.