Почему/не следует использовать "новый" оператор для создания экземпляра класса и почему?

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

Предположим, что у вас есть класс super complex, например:


class CDoSomthing {

    public:
        CDoSomthing::CDoSomthing(char *sUserName, char *sPassword)
        {
            //Do somthing...
        }

        CDoSomthing::~CDoSomthing()
        {
            //Do somthing...
        }
};

Как объявить локальный экземпляр внутри глобальной функции?


int main(void)
{
    CDoSomthing *pDoSomthing = new CDoSomthing("UserName", "Password");

    //Do somthing...

    delete pDoSomthing;
}

- или -


int main(void)
{
    CDoSomthing DoSomthing("UserName", "Password");

    //Do somthing...

    return 0;
}

Ответ 1

Предпочитают локальные переменные, если вам не требуется время жизни объекта, чтобы выйти за пределы текущего блока. (Локальные переменные являются вторым вариантом). Это просто легче, чем беспокоиться об управлении памятью.

P.S. Если вам нужен указатель, потому что вам нужно передать его другой функции, просто используйте адрес оператора:

SomeFunction(&DoSomthing);

Ответ 2

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

Выделение на стек работает очень хорошо, когда у вас ограниченный контроль над временем жизни объекта. Это означает, что вы не собираетесь передавать указатель или ссылку этого объекта на код вне области локальной функции. Это означает, что нет параметров, нет COM-вызовов, нет новых потоков. Довольно много ограничений, но вы правильно очищаете объект для вас при нормальном или исключительном выходе из текущей области (хотя вам, возможно, захочется прочитать правила разворачивания стека с виртуальными деструкторами). Самый большой недостаток распределения стека - стек обычно ограничен 4K или 8K, поэтому вы можете быть осторожны с тем, что вы на нем наложили.

Выделение на кучу с другой стороны потребует, чтобы вы вручную очистили экземпляр. Это также означает, что у вас есть большая свобода, как управлять временем жизни экземпляра. Вам нужно сделать это в двух сценариях: а) вы собираетесь передать этот объект из области видимости; или b) объект слишком велик, и выделение его в стеке может привести к переполнению стека.

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

Ответ 3

Вторая форма - это шаблон RAII (Инициализация ресурсов). Он имеет много преимуществ перед первым.

При использовании new вы должны использовать delete самостоятельно и гарантировать, что он всегда будет удален, даже если выбрано исключение. Вы должны все это гарантировать.

Если вы используете вторую форму, когда переменная выходит за пределы области видимости, она всегда очищается автоматически. И если генерируется исключение, стек распаковывается, и он также очищается.

Итак, вы должны предпочесть RAII (второй вариант).

Ответ 4

В дополнение к тому, что было сказано до сих пор, но есть дополнительные соображения производительности, которые необходимо учитывать, особенно в приложениях с интенсивным распределением памяти:

  • Использование new будет выделять память из кучи. В случае интенсивного (крайне частого) распределения и освобождения вы будете платить высокую цену в:
    • блокировка: куча - это ресурс, общий для всех потоков в вашем процессе. Операциям с кучей может потребоваться блокировка в менеджере кучи (выполняется для вас в библиотеке времени выполнения), что может значительно замедлить процесс.
    • фрагментация: фрагменты кучи. Вы можете увидеть время, когда malloc/new и free/delete вернутся к увеличению в 10 раз. Это связано с проблемой блокировки выше, так как требуется больше времени для управления фрагментированной кучей и больше очереди потоков, ожидающих блокировки лечения. (В Windows есть специальный флаг, который вы можете установить для менеджера кучи, чтобы он эвристически пытался уменьшить фрагментацию.)
  • Используя шаблон RAII, память просто снимается со стека. Stack - ресурс для каждой нити, он не фрагментируется, нет блокировки и может играть в ваших интересах с точки зрения местоположения памяти (т.е. Кэширование памяти на уровне ЦП).

Итак, когда вам нужны объекты для краткого (или ограниченного) периода времени, определенно используйте второй подход (локальная переменная в стеке.) Если вам нужно обмениваться данными между потоками, используйте new/malloc (на одном вам придется, с другой стороны, эти объекты, как правило, достаточно долговечны, поэтому вы платите по существу 0-стоимость по сравнению с менеджером кучи.)

Ответ 5

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

Ответ 6

Самое большое различие между ними состоит в том, что новый инициирует указатель на объект.

Создавая объект без new, инициированный объект сохраняется в стеке. Если он инициирован новым, он возвращает указатель на новый объект, который был создан в куче. Он фактически возвращает адрес памяти, указывающий на новый объект. Когда это происходит, вам нужно управлять памятью переменной. Когда вы закончите использовать переменную, вам нужно будет вызвать delete, чтобы избежать утечки памяти. Без нового оператора, когда переменная выходит за пределы области памяти, память будет автоматически освобождена.

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

Ответ 7

Mark Ransom прав, также вам нужно создать экземпляр new, если вы передадите переменную в качестве параметра функции CreateThread-esque.