Почему программисты C ++ минимизируют использование «новых»? [c++]


Answers

Поскольку стек является быстрым и надежным

В C ++ требуется только одна команда для распределения пространства - в стеке - для каждого объекта локальной области в заданной функции, и невозможно утечка какой-либо из этой памяти. Этот комментарий предполагал (или должен был предполагать) сказать что-то вроде «использовать стек, а не кучу».

Question

Я наткнулся на вопрос «Переполнение стека». Утечка памяти с помощью std :: string при использовании std :: list <std :: string> , и один из комментариев говорит об этом:

Прекратите использовать так много. Я не вижу причин, по которым вы использовали новое в любом месте. Вы можете создавать объекты по значению в C ++, и это одно из огромных преимуществ использования языка. Вам не нужно выделять все в стек *. Перестаньте думать, как программист на Java.

Я не совсем уверен, что он имеет в виду. Почему объекты должны создаваться по значению на C ++ как можно чаще, и какая разница делает его внутренне? Я неправильно понял ответ?

* Комментарий первоначально сказал «стек», с тех пор он был исправлен модератором до предполагаемой «кучи».




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

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

Умные указатели, такие как auto_ptr , shared_ptr решают проблему оборванных ссылок, но они требуют дисциплины кодирования и имеют другие проблемы (копируемость, циклы ссылок и т. Д.).

Кроме того, в сценариях с многопоточным сценарием new - это точка раздора между потоками; Может быть влияние производительности на чрезмерное использование new . Создание объекта стека по определению thread-local, поскольку каждый поток имеет свой собственный стек.

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




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

Самый простой способ сделать это - создать объект в автоматическом хранилище, поэтому C ++ знает, как его уничтожить, когда он выходит за рамки:

 {
    File foo = File("foo.dat");

    // do things

 }

Теперь заметим, что когда вы отвалитесь от этого блока после конечной скобки, foo выходит за рамки. C ++ вызовет его dtor автоматически для вас. В отличие от Java, вам не нужно ждать, пока GC ее обнаружит.

Если бы вы написали

 {
     File * foo = new File("foo.dat");

Вы хотели бы явно сопоставить это с

     delete foo;
  }

Или даже лучше, выделите свой File * как «умный указатель». Если вы не будете осторожны, это может привести к утечкам.

Сам ответ ошибочно полагает, что если вы не используете new вы не выделяете кучу; На самом деле, на C ++ вы этого не знаете. В лучшем случае вы знаете, что в стеке выделяется небольшой объем памяти, скажем, одного указателя. Однако рассмотрим, является ли реализация файла чем-то вроде

  class File {
    private:
      FileImpl * fd;
    public:
      File(String fn){ fd = new FileImpl(fn);}

FileImpl все равно будет выделен в стеке.

И да, вы должны быть уверены, что

     ~File(){ delete fd ; }

В классе; Без него вы будете утечка памяти из кучи, даже если вы явно не выделили кучу.




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

Class var;

Он помещается в стек.

Вам всегда придется вызывать уничтожение объекта, который вы разместили в куче, с новым. Это открывает потенциал для утечек памяти. Объекты, помещенные в стек, не подвержены утечке памяти!




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

Рассмотрим «осторожного» пользователя, который помнит об обертке объектов в интеллектуальных указателях:

foo(shared_ptr<T1>(new T1()), shared_ptr<T2>(new T2()));

Этот код опасен, потому что нет гарантии, что либо shared_ptr будет сконструирован до T1 либо T2 . Следовательно, если один из new T1() или new T2() терпит неудачу после того, как другой преуспеет, тогда первый объект будет просочиться, потому что shared_ptr существует, чтобы уничтожить и освободить его.

SolutIon: используйте make_shared .




Я склонен не соглашаться с идеей использования нового «слишком много». Хотя использование оригинального плаката нового с системными классами немного смешно. ( int *i; i = new int[9999]; Действительно? int i[9999]; гораздо яснее.) Я думаю, что это то, что получало козел комментатора.

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

Однако, когда вы работаете со своими собственными классами / объектами (например, с классом Line оригинального плаката), вы должны начать думать о проблемах, таких как объем памяти, постоянство данных и т. Д. Самостоятельно. На данный момент использование нескольких ссылок на одно и то же значение неоценимо - оно позволяет создавать такие конструкции, как связанные списки, словари и графики, где несколько переменных должны иметь не только одно и то же значение, но и ссылаться на один и тот же объект в памяти. Однако класс Line не имеет ни одного из этих требований. Таким образом, исходный код плаката фактически не нуждается в new .




Две причины:

  1. В этом случае нет необходимости. Вы делаете свой код излишне сложным.
  2. Он выделяет пространство в куче, и это означает, что вы должны помнить, что позже delete его, или это вызовет утечку памяти.



new - новый.

Напомним, почему goto настолько оскорблен: хотя он является мощным инструментом низкого уровня для управления потоком, люди часто использовали его излишне сложными способами, из-за которых сложный код не выполнялся. Более того, наиболее полезные и простые для чтения шаблоны были закодированы в структурированных операциях программирования (например, for или в while ); Конечный эффект заключается в том, что код, где goto является подходящим способом, довольно редок, если вы соблазняетесь писать goto , вы, вероятно, плохо что-то делаете (если вы действительно не знаете, что делаете).

new схож - он часто используется для того, чтобы сделать вещи излишне сложными, а тяжелее читать, а наиболее полезные шаблоны использования могут быть закодированы, закодированы в разные классы. Кроме того, если вам нужно использовать любые новые шаблоны использования, для которых еще нет стандартных классов, вы можете написать свои собственные классы, которые их кодируют!

Я бы даже утверждал, что new хуже, чем goto , из-за необходимости соединять new и delete утверждения.

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