У меня довольно большой, сложный алгоритм, который использует std::priority_queue
. В некоторых случаях я хочу, чтобы очередь была минимальной, а в других - максимальной. Поэтому я должен предоставить тип компаратора, который делает один или другой. Поскольку компаратор является параметром шаблона, он в основном является частью типа std::priority_queue
. Поэтому везде, где есть ссылка на очередь, код должен знать тип.
Один из вариантов, конечно, заключается в предоставлении пользовательского компаратора, который имеет состояние и выбирает между возрастающим и нисходящим при каждом вызове operator()
, но я пытаюсь избежать снижения производительности из-за наличия этой ветви для каждого сравнения , Другая альтернатива, которую я вижу, - это дублирование всего алгоритма, один для сортировки по возрастанию, а другой для убывания.
Есть ли лучшая альтернатива этой проблеме?
РЕДАКТИРОВАТЬ : Поскольку некоторые люди, похоже, очень хотят помочь, не понимая проблемы здесь, я попытаюсь немного уточнить.
Я реализовал алгоритм внешней сортировки (внешняя сортировка слиянием). Алгоритм использует std::priority_queue
на этапе объединения. Теперь сортировка может быть восходящей или нисходящей. Компаратор std::priority_queue
должен отличаться для этих двух случаев. Вы можете себе представить, что простой алгоритм сортировки может быть легко параметризован для обработки восходящего / нисходящего типа:
// psuedocode...
if (ascending) {
if (a > b) // do something
} else {
if (a < b) // do something
}
Одна проблема с этим заключается в том, что флаг ascending
должен проверяться на каждой итерации цикла сортировки. «jalf» предполагает, что компилятор может «встроить» эту ветку, но я не думаю, что видел, как это произошло, по крайней мере, с VC ++ (да, я смотрел на некоторую сборку).
Теперь, когда используется std::priority_queue
, чтобы сохранить некоторые вещи в отсортированном порядке, фактический тип очереди отличается для восходящих и нисходящих сортировок (поскольку компаратор должен быть разным, и это параметр шаблона). Итак, варианты:
- Дублировать алгоритм (функцию) для убывающего регистра.
- Параметризовать алгоритм в порядке сортировки и использовать одну очередь для возрастания и другую для убывания.
- Используйте функтор компаратора с одним фрагментом состояния (isAscending), чтобы
operator()()
мог выполнить правильное сравнение для возрастания и убывания.
- Шаблонизируйте функцию и потребуйте, чтобы компаратор был передан в качестве аргумента шаблона.
- Использовать указатель функции в качестве аргумента шаблона компаратора.
Обратите внимание, что я не переключаюсь между двумя различными режимами очереди во время алгоритма (как некоторые просили). Я устанавливаю очередь в качестве минимальной или максимальной очереди в начале алгоритма, и она остается таковой для этого конкретного вида.
Теперь сравните варианты выше.
Вариант 1 недоступен из-за нежелательного дублирования и обслуживания кода (хотя он, вероятно, даст лучший код).
Вариант 2 нежелателен из-за всех дополнительных проверок во время выполнения и ветвления, а также из-за ненужного создания очереди, которая не будет использоваться.
Вариант 3 более привлекателен, потому что ветвление инкапсулировано / изолировано в функторе компаратора, но все же требует проверки во время выполнения для каждого сравнения (это принципиально то, чего я хотел избежать).
Вариант 4 поднимает проблему на один уровень вверх и требует от вызывающего абонента знать и иметь доступ к функторам компаратора - что-то вроде грязного.
Вариант 5 выглядит довольно неплохо - он обеспечивает чистый код, позволяя функции компаратора различаться во время выполнения без изменения типа очереди. Логика принятия решения не должна быть доступна вызывающей стороне (как это было бы с вариантом 4).
Единственный минус, который я могу видеть с опцией 5, это то, что компилятор может не сможет встроить вызов через указатель функции - но в моем случае это было сделано, потому что вызываемая функция была определена локальнов том же переводчике.Если бы оно не было встроенным, я предполагаю, что вариант 3 был бы лучше, но в моем случае производительность была лучше с вариантом 5.
Кроме того, после того, как я понял, что вариант 5 возможен (я не сделал вво-первых), мне пришло в голову, что использование указателей на функции вместо функторов, вероятно, не так много, и я подозреваю, что люди иногда перепрыгивают через обручи, чтобы использовать функторы (как мне бы пришлось), когда указатель на функцию может сделать код намного чище(особенно, когда производительность не имеет значения).