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


Answers

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

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

Question

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

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

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




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

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

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

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

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




new - новый.

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

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

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

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




Объекты, созданные 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 ; }

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




Две причины:

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



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

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

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




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

Class var;

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

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