Распределение динамической и динамической памяти в сравнении со статическим экземпляром класса C ++

Мой конкретный вопрос заключается в том, что при реализации синглтон класс есть ли в C ++ существенные различия между двумя приведенными ниже кодами в отношении производительности, побочных проблем или чего-то подобного:

class singleton
{
// ...
static singleton& getInstance()
{
// allocating on heap
static singleton* pInstance = new singleton();
return *pInstance;
}
// ...
};

и это:

class singleton
{
// ...
static singleton& getInstance()
{
// using static variable
static singleton instance;
return instance;
}
// ...
};

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

ОБНОВИТЬ:

У меня есть интересные ответы и комментарии, которые я пытаюсь обобщить здесь. (Чтение подробных ответов рекомендуется для интересующихся.):

  • В синглтоне используя статический локальная переменная, деструктор класса автоматически вызывается при завершении процесса, тогда как в динамическое распределение В этом случае вы должны когда-нибудь управлять уничтожением объекта, например, с помощью умных указателей:
    static singleton& getInstance() {
static std::auto_ptr<singleton> instance (new singleton());
return *instance.get();
}
  • Синглтон, использующий динамическое распределение «Ленивее» чем статическая одноэлементная переменная, как в более позднем случае, необходимая память для одноэлементного объекта (всегда?) резервируется при запуске процесса (как часть всей памяти, необходимой для загрузки программы), и только вызов одноэлементного конструктора отложено до getInstance() Время звонка. Это может иметь значение, когда sizeof(singleton) большой.

  • Оба являются поточно-ориентированными в C ++ 11. Но с более ранними версиями C ++ это зависит от реализации.

  • Случай динамического размещения использует один уровень косвенности для доступа к одноэлементному объекту, тогда как в случае статического одноэлементного объекта прямой адрес объекта определяется и жестко кодируется во время компиляции.

П.С .: Я исправил терминологию, которую использовал в исходном сообщении, в соответствии с ответом @ TonyD.

16

Решение

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

  • new версия не будет вызывать деструктор объекта при завершении программы, но неnew версия будет: вы можете использовать умный указатель, чтобы исправить это

    • вам нужно быть осторожным, чтобы деструкторы некоторых статических / именных пространств-объектов не вызывали ваш синглтон после того, как деструктор его статического локального экземпляра запустился … если вас это беспокоит, возможно, вам следует прочитать немного больше о времени жизни синглтона и подходы к управлению ими. Современный C ++ Design у Андрея Александреску очень удобочитаемый.
  • в C ++ 03 это определяется реализацией, будет ли любой из них потокобезопасным. (Я полагаю, что GCC, как правило, так и есть, в то время как Visual Studio не комментирует, чтобы подтвердить / исправить.)

  • в C ++ 11 это безопасно: 6.7.4 «Если элемент управления вводит объявление одновременно во время инициализации переменной, параллельное выполнение должно ожидать завершения инициализации». (без рекурсии).

Обсуждение времени компиляции в сравнении с распределением времени выполнения & инициализация

Судя по тому, как вы сформулировали свое резюме и несколько комментариев, я подозреваю, что вы не совсем понимаете тонкий аспект распределения и инициализации статических переменных ….

Скажем, ваша программа имеет 3 локальных статических 32-разрядных intс — a, b а также c — в разных функциях: компилятор может скомпилировать двоичный файл, который говорит загрузчику ОС оставить 3×32-бит = 12 байт памяти для этой статики. Компилятор решает, какое смещение имеет каждая из этих переменных: он может поставить a по смещению 1000 гекс в сегменте данных, b на 1004, и c на 1008. Когда программа выполняется, загрузчик ОС не должен выделять память для каждого в отдельности — все, что он знает, это всего 12 байтов, которые могут быть запрошены или нет специально для инициализации 0, но это может захотеть сделать так или иначе, чтобы убедиться, что процесс не может видеть остаточный объем памяти из программ других пользователей. Инструкции машинного кода в программе обычно жестко кодируют смещения 1000, 1004, 1008 для доступа к a, b а также c — поэтому не требуется выделение этих адресов во время выполнения.

Динамическое распределение памяти отличается тем, что указатели (скажем, p_a, p_b, p_c) будут даны адреса во время компиляции, как только что описано, но дополнительно:

  • указанная память (каждый из a, b а также c) должен быть найден во время выполнения (обычно, когда статическая функция выполняется впервые, но компилятору разрешено делать это раньше, как указано в моем комментарии к другому ответу), и
    • если операционная система в данный момент выделяет слишком мало памяти для динамического выделения памяти, то программная библиотека запросит у ОС дополнительную память (например, используя sbreak()) — которую ОС обычно удаляет из соображений безопасности
    • динамические адреса, выделенные для каждого из a, b а также c должны быть скопированы обратно в указатели p_a, p_b а также p_c,

Этот динамический подход явно более запутанный.

7

Другие решения

Основное отличие заключается в том, что с использованием местного static объект будет уничтожен при закрытии программы, вместо этого объекты, выделенные в куче, будут просто оставлены без уничтожения.

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

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

Программа, которая падает перед вводом первой инструкции main или после выполнения последней инструкции main сложнее отлаживать.

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

2

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector