Последствия только использования стека в C ++ - PullRequest
16 голосов
/ 12 сентября 2010

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

Программа представляет собой небольшую графическую программу, и большая часть рассматриваемой передачи - это математические объекты Vector (3 кортежа).Есть несколько больших объектов контроллера, но нет ничего более сложного, чем это.

Мне трудно найти убийственный аргумент против использования только стека.

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

Что касается профессионалов, я считаю, что стек быстрее выделяет / освобождает память и имеет постоянное время выделения.1010 * Единственный важный аргумент, который я могу придумать, заключается в том, что стек может переполниться, но я предполагаю, что это вряд ли произойдет?Есть ли другие аргументы против использования только стека / передачи по значению, а не передачи по ссылке?

Ответы [ 13 ]

14 голосов
/ 12 сентября 2010

Подтип-полиморфизм - это случай, когда передача по значению не сработает, потому что вы бы разделили производный класс до его базового класса. Может быть, для некоторых использование subtyping-полиморфизма - плохой дизайн?

6 голосов
/ 12 сентября 2010

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

Единственный признак нарушения дизайна, который я вижу здесь, - это слепая религия вашего друга.

Тем не менее, есть несколько подписей, которые не приносят много на стол.Принимать const по значению может быть глупо, потому что, если вы пообещаете не изменять объект, вы можете не создавать свою собственную копию.Конечно, если это не примитив, и в этом случае компилятор может быть достаточно умен, чтобы по-прежнему ссылаться.Или иногда неудобно принимать указатель на указатель в качестве аргумента.Это добавляет сложности;вместо этого вы могли бы справиться с этим, взяв ссылку на указатель, и получить тот же эффект.

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

  1. Если вам нужно изменить аргумент для своих собственных нужд, но не хотите влиять на клиента, тогда возьмитеаргумент по значению.
  2. Если вы хотите предоставить клиенту услугу, а клиент не имеет тесной связи со службой, рассмотрите возможность принятия аргумента по ссылке.
  3. Если клиенттесно связаны со службой, тогда рассмотрите возможность не принимать аргументов, а написать функцию-член.
  4. Если вы хотите написать функцию службы для семейства клиентов, которые тесно связаны со службой, но очень отличаются друг от друга, тогда рассмотрите возможностьвзяв ссылочный аргумент, и, возможно, сделайте функцию другом клиентов, которым нужна эта дружба.
  5. Если вам вообще не нужно менять клиента, рассмотрите возможность использования const-ссылки.
6 голосов
/ 12 сентября 2010

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

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

1 Различные атрибуты, включая CA Hoare (хотя, по-видимому, он отказывается от этого).

5 голосов
/ 12 сентября 2010

Я думаю, что в самом вопросе есть огромное недоразумение.

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

Стек против распределения кучи

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

Возможно, нетвозможно в нескольких ситуациях:

  • Виртуальное строительство (представьте себе Фабрику)
  • Совместное владение (хотя вы всегда должны избегать его)

И я могу пропустить некоторые, но в этом случае вам следует использовать SBRM (Scope Bound Resources Management), чтобы использовать возможности управления временем жизни стека, например, с помощью умных указателей.

Передать:значение, ссылка, указатель

Прежде всего, существует разница семантика :

  • значение, константная ссылка: переданный объектне будет изменен методом
  • ссылка: переданный объект может быть изменен методом
  • указатель / постоянный указатель: то же самое, что и ссылка (для поведения), но может быть нулевым

Обратите внимание, что некоторые языки (функциональный вид, например, Haskell) не предлагают ссылку / указатель по умолчанию.Значения неизменны после создания.Помимо некоторых обходных путей для работы с внешней средой, они не ограничены этим использованием, и это каким-то образом облегчает отладку.

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

Наконец, Полиморфизм не допускает семантику передачи по значению.

Теперь давайте поговорим о производительности.

Обычно хорошо принято, что встроенные модули должны передаваться по значению (чтобы избежать косвенного обращения), а определяемые пользователем классы big должны передаваться по ссылке./ указатель (чтобы избежать копирования). большой на самом деле обычно означает, что конструктор копирования не является тривиальным.

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

Object foo(Object d) { d.bar(); return d; }

int main(int argc, char* argv[])
{
  Object o;
  o = foo(o);
  return 0;
}

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

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

5 голосов
/ 12 сентября 2010

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

Хотя это неправильно в C ++ по чисто техническим причинам, всегда используя парользначение-значение - это достаточно хорошее приближение для начинающих - оно, безусловно, намного лучше, чем передавать все по указателям (или, возможно, даже передавать все по ссылке).Это сделает некоторый код неэффективным, но, эй!Пока это не беспокоит вашего друга, не будьте чрезмерно обеспокоены этой практикой.Просто напомните ему, что когда-нибудь он захочет пересмотреть.

С другой стороны, это:

Есть несколько больших объектов-контроллеров, но нет ничего более сложного, чем это.

- это проблема.Ваш друг говорит о сломанном дизайне, и тогда весь код использует несколько трехмерных векторов и большие управляющие структуры? То, что - это сломанный дизайн.Хороший код достигает модульности за счет использования структур данных.Кажется, что это не так.

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

3 голосов
/ 12 сентября 2010

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

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

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

Таким образом, реальная проблема должна заключаться в семантике.Как вы хотите, чтобы ваш код вел себя?Иногда семантика ссылок - это то, что вам нужно, и тогда вам следует перейти по ссылке.Если вы конкретно хотите / нуждаетесь в семантике значений, то вы передаете по значению.

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

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

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

Пока что компилятор не имеетдействительно подведи меня.Я уверен, что столкнусь со случаями, когда мне придется вернуться и изменить некоторые вызовы на передачу по ссылке, но я сделаю это, когда узнаю, что производительность

  • является проблемой, и
  • компилятору не удалось применить копию elision
3 голосов
/ 12 сентября 2010

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

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

Например, библиотека Windows Forms использует структуру Rectangle, которая имеет 4 члена, Apple QuartzCore также имеет структуру CGRect, и эти структуры всегда передаются по значению .Я думаю, что мы можем сравнить это с Vector с переменной с плавающей точкой 3.

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

3 голосов
/ 12 сентября 2010

Я бы сказал, что Не использование указателей в C - признак новичка в программировании.

Похоже, ваш друг боится указателей.

Помните, что указатели C ++ были унаследованы от языка C, а C был разработан, когда компьютеры были гораздо менее мощными. Тем не менее, скорость и эффективность остаются жизненно важными до сегодняшнего дня.

Итак, зачем использовать указатели? Они позволяют разработчику оптимизировать работу программы или использовать меньше памяти, чем в противном случае! Ссылка на место в памяти данных гораздо эффективнее, чем копирование всех данных.

Указатели, как правило, представляют собой концепцию, которую трудно понять тем, кто начинает программировать, потому что все проведенные эксперименты включают небольшие массивы, возможно, несколько структур, но в основном они состоят из работы с парой мегабайт (если вам повезет ) когда у вас есть 1 ГБ памяти, лежащей вокруг дома. В этой сцене пара МБ - это ничто, и обычно это слишком мало, чтобы оказать существенное влияние на производительность вашей программы.

Так что давайте немного преувеличиваем. Подумайте о массиве char с 2147483648 элементами - 2 ГБ данных - который вам нужно передать в функцию, которая будет записывать все данные на диск. Теперь, какой метод вы думаете, будет более эффективным / быстрее?

  • Передача по значению, для которой потребуется повторно скопировать эти 2 ГБ данных в другое место в памяти, прежде чем программа сможет записать данные на диск, или
  • Передача по ссылке, которая будет просто ссылаться на эту ячейку памяти.

Что происходит, когда у вас просто нет 4 ГБ ОЗУ? Будете ли вы тратить $ и покупать чипы оперативной памяти только потому, что боитесь использовать указатели?

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

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

Удачи.

2 голосов
/ 12 сентября 2010

Единственный важный аргумент, который я могу придумать, заключается в том, что стек может переполниться, но я предполагаю, что это вряд ли произойдет?Есть ли другие аргументы против использования только стека / передачи по значению, а не передачи по ссылке?

Ну, черт возьми, с чего начать ...

  1. Как вы упоминаете, "в коде происходит много ненужного копирования".Допустим, у вас есть цикл, где вы вызываете функцию для этих объектов.Использование указателя вместо дублирования объектов может ускорить выполнение на один или несколько порядков.

  2. Нельзя передавать структуры данных, массивы и т. Д. Переменного размера вокругстек.Вы должны динамически распределить его и передать указатели или ссылку на начало.Если ваш друг не сталкивался с этим, то да, он «новичок в C ++».

  3. Как вы упоминаете, рассматриваемая программа проста и в основном использует довольно маленькие объекты, такие как графика3 кортежа, которые, если элементы двойные, будут иметь длину 24 байта.Но в графике принято иметь дело с массивами 4x4, которые обрабатывают как вращение, так и перемещение.Это будет 128 байтов за штуку, поэтому, если программа, которая должна иметь дело с ними, будет в пять раз медленнее для каждого вызова функции с передачей по значению из-за увеличенного копирования.При передаче по ссылке передача массива из 3-х кортежей или 4x4 в 32-битном исполняемом файле будет просто дублировать один 4-байтовый указатель.

  4. На ЦП с большим регистромтакие архитектуры, как ARM, PowerPC, 64-битные x86, 680x0 - но не 32-битные x86 - указатели (и ссылки, которые являются тайными указателями в причудливой синтетической одежде) обычно передаются или возвращаются в регистр, который по-настоящему быстро сравниваетсяк доступу к памяти, участвующей в операции стека.

  5. Вы упомянули невероятную нехватку места в стеке.И да, это так для небольшой программы, которую можно написать для назначения класса.Но пару месяцев назад я отлаживал коммерческий код, который был, вероятно, на 80 вызовов функций ниже main ().Если бы они использовали передачу по значению вместо передачи по ссылке, стек был бы огромен.И чтобы ваш друг не подумал, что это «неправильный дизайн», на самом деле это был браузер на основе WebKit, реализованный в Linux с использованием GTK +, и все это очень современно, а глубина вызова функции является нормальной для профессионального кода..

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

Я мог бы продолжать и продолжать.

Если ваш друг интересуется графикой, он должен взглянуть нанекоторые из распространенных API-интерфейсов, используемых в графике: OpenGL и XWindows в Linux, Quartz в Mac OS X, Direct X в Windows.И он должен взглянуть на внутреннюю часть больших систем C / C ++, таких как движки рендеринга HTML WebKit или Gecko, любой из браузеров Mozilla или GTK + или Qt GUI.Все они проходят мимо чего-то намного большего, чем одно целое число или число с плавающей запятой по ссылке, и часто заполняют результаты по ссылке, а не как возвращаемое значение функции.

Никто не сталкивается с серьезными реальными задачами C / C ++ - и яmean nobody - передает структуры данных по значению.Для этого есть причина: это просто переворачивает неэффективно и проблемно.

2 голосов
/ 12 сентября 2010

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

Маленькие кортежи или векторы - это очень простой тип структуры данных. Более сложные структуры данных обмениваются информацией, и этот обмен не может быть представлен непосредственно как значения. Вам нужно либо использовать ссылки / указатели, либо что-то, имитирующее их, например, массивы и индексы.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...