Почему я не должен использовать оператор "new" для создания экземпляра класса и почему? - PullRequest
10 голосов
/ 21 февраля 2009

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

Предположим, у вас был суперкомплекс класс, такой как:


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;
}

Ответы [ 7 ]

27 голосов
/ 21 февраля 2009

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

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

SomeFunction(&DoSomthing);
22 голосов
/ 21 февраля 2009

При объявлении переменной в стеке существует два основных соображения по сравнению с кучей - контроль времени жизни и управление ресурсами.

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

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

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

13 голосов
/ 21 февраля 2009

Вторая форма - это так называемый шаблон RAII (Resource Acquisition Is Initialization). Он имеет много преимуществ перед первым.

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

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

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

6 голосов
/ 21 февраля 2009

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

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

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

3 голосов
/ 21 февраля 2009

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

2 голосов
/ 21 февраля 2009

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

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

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

0 голосов
/ 21 февраля 2009

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

...