Я работал с операторами сдвига разряда (см. мое Равенство Битового массива вопроса), и, ТАКИМ ОБРАЗОМ, пользователь указал на ошибку в моем вычислении моего операнда сдвига - я вычислял диапазон [1,32] вместо [0,31] для интервала (Ура для ТАКИМ ОБРАЗОМ сообщество!)
В решении проблемы я был удивлен найти следующее поведение:
-1 << 32 == -1
На самом деле это казалось бы этим n << s
компилируется (или интерпретируется CLR - я не проверял IL), как n << s % bs(n)
где бакалавр наук (n) = размер, в битах, n.
Я ожидал бы:
-1 << 32 == 0
Казалось бы, что компилятор понимает, что Вы смещаетесь вне размера цели и исправляете свою ошибку.
Это - просто академический вопрос, но кто-либо знает, определяется ли это в спецификации (я ничего не мог бы найти в 7.8 Операторах сдвига), просто случайный факт неопределенного поведения, или есть ли случай, где это могло бы произвести ошибку?
Добавить к прениям здесь.
Известны проблемы с вывозом мусора, и понимание их помогает понять, почему их нет в C++.
1. Производительность?
Первая жалоба часто касается производительности, но большинство людей на самом деле не понимают, о чем они говорят. Как проиллюстрировано Мартином Беккетом
, проблемой может быть не производительность как таковая, а предсказуемость производительности.
В настоящее время существует 2 семейства GC, которые широко развернуты:
Mark And Sweep
быстрее (меньшее влияние на общую производительность), но он страдает от синдрома «заморозить мир»: т.е. когда GC ударяется, все остальное останавливается до тех пор, пока G Если вы хотите построить сервер, который отвечает за несколько миллисекунд... некоторые транзакции не оправдают ваших ожиданий:)
Проблема Подсчет ссылок
отличается: подсчет ссылок добавляет накладные расходы, особенно в средах Multi-Threading, потому что требуется атомарный подсчет. Кроме того, существует проблема эталонных циклов, поэтому вам нужен умный алгоритм, чтобы обнаружить эти циклы и устранить их (как правило, реализовать «заморозить мир» тоже, хотя и менее часто). В целом, на сегодняшний день этот тип (даже если обычно он более чувствителен или, скорее, реже замерзает) медленнее, чем Mark And Sweep
.
Я видел статью реализаторов Eiffel, которые пытались реализовать Отсчет
Сборщик мусора, который имел бы сходную глобальную производительность с Mark And Sweep
без аспекта «Заморозить мир». Требовалась отдельная резьба для GC (типовая). Алгоритм был немного пугающим (в конце) но в статье хорошо поработали над введением понятий по одному и показав эволюцию алгоритма от «простой» версии к полноценной. Рекомендуется читать, если только я могу положить руки обратно в файл PDF...
2. Acquisition Is Initialization (RAI)
В C++
обычно используется идиома, в соответствии с которой права собственности на ресурсы переносятся в объект для обеспечения их надлежащего освобождения. Он в основном используется для памяти, так как у нас нет сбора мусора, но он также полезен, тем не менее, для многих других ситуаций:
Идея состоит в правильном управлении временем жизни объекта:
Проблема ГК в том, что если он помогает с первым и в конечном итоге гарантирует, что позже... этого «предельного» может быть недостаточно. Если вы разблокируете блокировку, вам бы очень хотелось, чтобы она была разблокирована сейчас, чтобы она не блокировала дальнейшие вызовы!
Языки с GC имеют два порядка работы:
с помощью конструкции
... но это явный (слабый) RAI, в то время как в C++ RAI неявный, так что пользователь НЕ МОЖЕТ невольно совершить ошибку (опустив с помощью ключевого слова
) 3. Смарт-указатели
Смарт-указатели часто отображаются как серебряный маркер для обработки памяти в C++
. Часто я слышал: нам ведь не нужен ГК, так как у нас есть умные указатели.
Нельзя больше ошибаться.
Умные указатели помогают: auto _ ptr
и unique _ ptr
используют концепции RAI, что действительно очень полезно. Они настолько просты, что вы можете написать их сами довольно легко.
Когда нужно разделить владение, это становится сложнее: вы можете поделиться между несколькими потоками, и есть несколько тонких проблем с обработкой подсчета. Поэтому, естественно, следует к shared _ ptr
.
Это здорово, это то, что Boost в конце концов, но это не серебряная пуля. На самом деле, основная проблема с shared _ ptr
состоит в том, что он эмулирует GC, реализованный Ссылочным подсчетом
, но вы должны реализовать обнаружение цикла самостоятельно... Urg
Конечно, есть эта слабая _ ptr
вещь, но я, к сожалению, уже видел утечки памяти, несмотря на использование shared _ ptr
из-за этих циклов... и когда вы находитесь в многопоточной среде, это очень трудно обнаружить!
4. Какое решение?
Нет серебряной пули, но, как всегда, это определенно осуществимо. В отсутствие GC необходимо четко определить право собственности:
слабого _ ptr
Так что было бы здорово иметь GC... однако это не тривиальная проблема. И в то же время нам нужно просто засучить рукава.
-121--569529-Указатели и ссылки похожи, но различаются несколькими способами:
T * a
и T & b
доступ к переменным и функциям элемента осуществляется с помощью a- > member
и b.member
соответственно. a = 0
является законным, но b = 0
или что-либо в этом отношении не является. a = & b
является законным, но int c; b = c;
- нет ( T & b = c
- ссылки могут быть установлены только при их инициализации). (Спасибо Майк Д.) const
ссылки могут. T * *
)но ссылки не могут ссылаться на другие ссылки (то есть нет такой вещи, как T & &
, хотя обратите внимание, что C++ 0x будет использовать T & &
для определения семантики перемещения). В результате массивы ссылок будут недоступны. (Спасибо AshleysBrain) Можно задаться вопросом, почему у нас вообще есть ссылки, а не только постоянно использовать указатели (как в C). Причина - перегрузка оператора или определенные операторы.
Рассмотрим оператор назначения. Какой будет синтаксис функции без ссылок? Если бы это был T * оператор = (T * lhs, T rhs)
, то нам пришлось бы писать такие вещи, как:
int a(1);
&a = 2;
По сути, ссылки позволяют нам иметь функции l-значений без необходимости ссылки указателя и отмены синтаксиса.
-121--4553952-Я считаю, что здесь находится соответствующая часть спецификации:
Для предопределенных операторов число битов для сдвига вычисляется следующим образом:
Когда тип x равен int или uint, число сдвигов задается значением младшие пять битов счетчика. Другими словами, вычисляется число сдвигов из числа & 0x1F.
Если тип x является длинным или ulong, число сдвигов задается шесть битов подсчета низкого порядка. Другими словами, вычисляется число сдвигов из числа & 0x3F.
Если результирующее число сдвигов равно нулю, операторы сдвига просто возвращают значение x.
Значение 32
равно 0x20
. Выражение 0x20 & 0x1F
вычисляется как 0
. Следовательно, число сдвигов равно нулю, и сдвиг не выполняется; выражение -1 < < 32
(или любое x < < 32
) возвращает только исходное значение.