Как бы вы заменили ключевое слово «новое»?

Была статья, которую я нашел давно (я не могу найти ее в банкомате), в которой изложены причины, по которым новое ключевое слово в C++ плохое. Я не могу вспомнить все причины, но две, которые я помню больше всего, это то, что вы должны сопоставлять new с delete, new[] с delete[] и вы не можете использовать #define с new, как вы могли бы с malloc.

Я разрабатываю язык, поэтому хочу спросить, как бы вы изменили язык C++, чтобы новый был более дружелюбным. Не стесняйтесь заявлять о проблемах с новыми и статьями. Я хотел бы найти ссылку на статью, но я помню, что она была длинной и была написана профессором (IIRC) известной школы.


person Community    schedule 13.08.2009    source источник
comment
malloc(), очевидно, тоже плохо, так как вы должны сопоставить его с free()   -  person Matthew Scharley    schedule 13.08.2009
comment
Я бы не позволил #define-ability влиять на какие-либо решения о синтаксисе, даже если это драконы...   -  person Drew Hall    schedule 13.08.2009
comment
Если вы не можете сформулировать свое собственное мнение о плюсах и минусах этого языкового решения, что заставляет вас думать, что вы готовы его улучшить?   -  person Jim Lewis    schedule 13.08.2009
comment
Это та статья, о которой вы думали? c2.com/cgi/wiki?NewConsideredHarmful   -  person mattwright    schedule 13.08.2009
comment
Или это может быть рассматриваемая статья: scs.stanford. edu/~dm/home/papers/c++-new.html   -  person TheUndeadFish    schedule 14.08.2009
comment
Джим Льюис: Я собираю мнения/провожу исследования. Я не хочу ничего упускать из виду   -  person    schedule 14.08.2009


Ответы (8)


Проблема соответствует new, delete, new[], delete[]

Ничего особенного.
Вы должны обернуть распределение памяти внутри класса, чтобы это не сильно повлияло на обычных пользователей. Один объект может быть обернут интеллектуальным указателем. В то время как массив может быть представлен std::Vector‹›

нельзя использовать #define с new, как с malloc.

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

person Community    schedule 13.08.2009

Я не вижу причин заменять ключевое слово new чем-то другим (и, кажется, комитет C++ согласен со мной). Он понятен и делает то, что должен. Вы можете переопределить operator new в своем классе, не нужно использовать определения.

Чтобы устранить проблему new[]/delete[], вы можете использовать std::vector.

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

person Community    schedule 13.08.2009
comment
Согласовано. Вместо этого я бы заменил новое ключевое слово в Java, поскольку вы все равно не можете поместить объект в стек! - person Drew Hall; 13.08.2009
comment
Обратите внимание, что boost/tr1/std::array не является/не будет заменой new[]/delete[]. (Это будет std::vector.) Что касается вашего аргумента: с одной стороны, я вижу преимущество в том, что RTE заботится о delete по сравнению с delete[]. OTOH, C++ предлагает множество альтернатив (например, std::vector), позволяющих вообще избежать динамического распределения. Но, как я сказал в stackoverflow.com/questions/1245430/, это в основном вопрос о том, как преподается C++, а не что он предлагает. - person sbi; 13.08.2009
comment
tr1::array заменяет new[]/delete[]. Он может выделить массив в стеке. std::vector всегда выделяет память в куче. - person Kirill V. Lyadvinsky; 13.08.2009
comment
Нет, Jla3ep, array не замена new[]. Если вы используете new[], то это потому, что вы не знаете количество элементов до времени выполнения. Но array имеет нетиповой параметр шаблона, указывающий его размер, и такой параметр должен быть константой времени компиляции. Если вы знаете необходимое количество элементов во время компиляции, то вам вообще не следовало использовать динамическое размещение. array заменяет массивы фиксированного размера, а не те, которые выделяются с помощью new[]. Для этого используйте vector. - person Rob Kennedy; 13.08.2009
comment
Ах да... с той стороны :) Исправил свой ответ. - person Kirill V. Lyadvinsky; 13.08.2009

Я бы дал ему семантику new в С# (более или менее):

  1. Выделяет память для объекта.
  2. Инициализирует память, устанавливая для переменных-членов их значения по умолчанию (обычно 0 для значений, null для ссылок).
  3. Инициализирует механизм динамической привязки объекта (виртуальные таблицы в C++, таблицы определения типов для управляемых виртуальных машин).
  4. Вызывает конструктор, после чего виртуальные вызовы работают должным образом.
  5. Для языка без сборки мусора (на данный момент для нового языка) верните smart_ptr или аналогичный результат вызова.

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

person Community    schedule 13.08.2009
comment
Не знаю, мне нравится, как это делает D. Сборка мусора, но возможность делать RAII. - person GManNickG; 13.08.2009
comment
Между try/finally и using я никогда не чувствую, что что-то упускаю. - person Sam Harwell; 13.08.2009
comment
Это предполагает, что все типы значений малы (т.е. помещаются в стеке). Я бы сказал, что огромные матрицы являются типами значений, но определенно не принадлежат стеку. - person Drew Hall; 13.08.2009
comment
@Drew: На самом деле это не тема для подробностей, но я действительно работал над некоторым интересным (по крайней мере для меня) сотрудничеством между матрицами и GC / JIT, чтобы соответствовать скоростям C / C ++ в управляемой виртуальной машине для научных вычислений. Если бы я не был привязан сюда из-за учебы, возможно, именно в этом районе я пытался бы найти работу. :D - person Sam Harwell; 13.08.2009
comment
Мне не хватает using для составных подобъектов (т.е. для полей-членов). - person Pavel Minaev; 13.08.2009
comment
@Pavel, это никогда не проблема, потому что каждый класс с полями IDisposable реализует IDisposable. - person Sam Harwell; 14.08.2009
comment
Вы упускаете суть — когда я пишу класс с IDisposaable полями, мне приходится самому реализовывать IDisposable. Я не хочу этого делать — я просто хочу сказать, что вот эти поля Foo и Bar принадлежат мне, поэтому сделайте меня IDisposable, а когда кто-то вызовет Dispose на меня, делегируйте им полномочия с поддержкой обычного шаблона C# IDisposable (с защищенным методом с bool и т. д.). Другими словами, как C++/CLI уже это делает. - person Pavel Minaev; 14.08.2009
comment
Уточнение моего предыдущего комментария: Огромные матричные ОБЪЕКТЫ могут/должны быть в стеке, но их ДАННЫЕ явно не должны быть. Это меня беспокоило... - person Drew Hall; 17.08.2009

Используйте сборку мусора, чтобы вам никогда не приходилось сопоставлять новое с чем-либо.

person Community    schedule 13.08.2009
comment
Соглашаться. C++ использует очень универсальный механизм сборки мусора, называемый умными указателями. - person Martin York; 13.08.2009
comment
Слышу, слышу! RAII намного мощнее недетерминированного GC! - person Drew Hall; 13.08.2009
comment
Но интеллектуальные указатели работают намного медленнее, чем современные сборщики мусора. - person Sam Harwell; 13.08.2009
comment
И интеллектуальные указатели не выполняют ту же работу - циклы освобождения - и обычно требуют блокировки (по крайней мере, на уровне шины) для каждого захвата или освобождения, а не грубой блокировки, требуемой gc. - person Pete Kirkham; 13.08.2009
comment
это верный момент. Я люблю RAII так же, как и все остальные, но утверждать, что он полностью заменяет сборщик мусора, просто глупо. Сборщик мусора был бы более эффективным и обрабатывал бы циклы. Недостатком является то, что вы теряете детерминированное разрушение. Я не понимаю, почему язык не может предложить и то, и другое. Позвольте мне выбрать при определении типа, как он должен быть уничтожен - GC или детерминированный. - person jalf; 13.08.2009
comment
У меня есть доказательство — оно не уместится здесь — показывающее, что GC обязательно не более эффективны, чем RAII, потому что для каждой реализации GC есть тривиальная реализация RAII поверх этого. - person MSalters; 13.08.2009
comment
Почему минус? Реально помогает, если расскажешь. GC может быть лучшим или не лучшим, но так ли он плох по своей сути? - person djna; 13.08.2009

Используя классы контейнеров STL и различные boost:smart_ptrs, нет необходимости явно вызывать new или delete в коде C++.

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

Но C++ и STL очень и очень усердно работают над тем, чтобы вы могли обращаться с большинством объектов как с объектами-значениями, поэтому вы можете создавать объекты, а не указатели, и просто использовать их.

Учитывая все это, нет особой необходимости заменять новый оператор — и это может привести к множеству проблем, будь то необходимость использования сборщика мусора или снижение тонкого низкоуровневого управления, которое C++ предлагает программистам.

person Community    schedule 13.08.2009

Если ваш новый язык является сборщиком мусора, вы можете не использовать ключевое слово new. Это то, что сделал Python (и Lisp сделал почти 5 десятилетий назад!). Также см. ответ Питера Норвига на аналогичный вопрос здесь. (Разве отсутствие «новости» не является хорошей новостью?)

person Community    schedule 13.08.2009
comment
В обычном лиспе эквивалентом «нового» является «создать экземпляр», поэтому это функция, а не ключевое слово/специальная форма, но в остальном она похожа (взяв класс объекта и его параметры инициализации). - person Pete Kirkham; 13.08.2009
comment
make-instance является частью CLOS, которая, в свою очередь, реализована в самом Lisp. (SICP и The Art of the Metaobject Protocol прольют на это больше света). Обратите внимание, что make-instance не выполняет никаких вызовов уровня виртуальной машины для создания нового экземпляра объекта. На самом деле виртуальная машина Lisp не знает о существовании классов и объектов. Так что это нельзя сравнивать со стратегиями реализации, за которыми следует Java и т. д. - person Vijay Mathew; 13.08.2009
comment
SCIP иллюстрирует объектную систему на схеме в качестве учебного пособия. Профессиональные реализации Common Lisp, такие как Allegro, реализуют «родной» CLOS, а не используют conses для значительного прироста производительности. - person Pete Kirkham; 13.08.2009
comment
Согласовано. Кстати, как реализован CLOS? Он реализован в самом Лиспе. В конце концов, объекты оказываются процедурами с состоянием. Это просто показано в SICP и подробно описано в The Art of the Metaobject Protocol. Дело в том, что make-instance не следует путать с new. - person Vijay Mathew; 14.08.2009

Иногда вы хотите заменить конструктор фабрикой. Это хорошо известный рефакторинг. Заменить конструктор фабричным методом. Так может это и имелось в виду в статье?

Между прочим, вы часто будете видеть, как прямые вызовы new заменяются фабричным методом.

Среды внедрения зависимостей, такие как Unity, выводят эту концепцию на новый уровень. Как видно из следующего кода C#, для создания интерфейса IMyClass не применяется "новое":

IUnityContainer myContainer = new UnityContainer();
myContainer.RegisterType<IMyClass, SomeClass>();
IMyClass thing = myContainer.Resolve<IMyClass>();
person Community    schedule 13.08.2009
comment
В одном из моих экспериментальных проектов на C++ я фактически написал свои объявления классов в XML и сгенерировал для них заголовки, где конструкторы были защищены, но предоставляли статический метод Create, который придавал им правильную семантику и возвращал интеллектуальные указатели. :) - person Sam Harwell; 13.08.2009

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

Если бы у вас было удаление хвостовых вызовов и продолжения, вам было бы все равно - все объекты могут быть созданы в стеке и иметь неограниченную протяженность - объект может существовать до тех пор, пока вы не вызовете продолжение, которое соответствует выходу объекта из области видимости и его уничтожению. . Затем вам может понадобиться что-то для сборки мусора или иного сжатия стека, чтобы он не заполнялся ненужными объектами (Chicken Scheme и TinyOS 2 — это два разных примера создания эффекта динамической памяти без динамической памяти либо во время выполнения, либо время компиляции; Chicken Scheme не допускает RAII, а TinyOS не допускает истинного динамического распределения), хотя для большого объема кода такая схема не будет сильно отличаться от RAII с возможностью выбора изменения порядка объекты уничтожаются.

person Community    schedule 13.08.2009