У меня есть две функции, критичные к производительности, как это:
insertExpensive(Holder* holder, Element* element, int index){
//............ do some complex thing 1
holder->ensureRange(index);//a little expensive
//............ do some complex thing 2
}
insertCheap(Holder* holder, Element* element, int index){
//............ do some complex thing 1
//............ do some complex thing 2
}
Как сгруппировать 2 функции вместе, чтобы повысить ремонтопригодность?
Решение 1.
insertExpensive(Holder* holder, Element* element, int index){
do1();
holder->ensureRange(index);//a little expensive
do2();
}
insertCheap(Holder* holder, Element* element, int index){
do1();
do2();
}
Это было бы некрасиво.
Это также нецелесообразно, если do2
хочу некоторые локальные переменные из do1
,
Решение 2.
insert(Holder* holder, Element* element, int index, bool check){
//............ do some complex thing 1
if(check)holder->ensureRange(index);//a little expensive
//............ do some complex thing 2
}
Это стоит условная проверка для каждого звонка.
Решение 3. (проект)
template<bool check> insert(Holder* holder, Element* element, int index){
//............ do some complex thing 1 (Edit2 from do1());
bar<check>();
//............ do some complex thing 2 (Edit2 from do2());
}
template <>
inline void base_template<true>::bar() { holder->ensureRange(index); }
template <>
inline void base_template<false>::bar() { }
Избыточность и ненужные сложности?
Изменить 1:
Приоритет критериев того, насколько хороший подход, сортируются следующим образом:
1. Лучшая производительность
2. Меньше дубликата кода
3. Меньше всего строки кода
4. Легче читать для эксперта & начинающий
Изменить 2: отредактируйте 3-е решение. Спасибо Мвидельгауз и Вольф.
Ваше решение 2 на самом деле не так уж плохо. Если этот код находится внутри заголовка, он неявно рассматривается как встроенный код. (Я написал это явно) Если вы вызываете его с помощью true или false, компилятор может удалить оператор if, хотя это зависит от ряда факторов, которые необходимо знать, если он это сделает. (Размер всего тела после врезки, постоянная видимость, тюнинг …)
inline void insert(Holder* holder,Element* element,int index, bool check){
do1();
if (check)
holder->ensureRange(index);//a little expensive
do2();
}
Решение 3 — это именно то, чего вы хотите достичь, так как шаблоны требуют создания новых экземпляров функций для каждого отдельного вызова, поэтому он удалит мертвый код. Однако это может быть написано очень похоже на то, как вы написали решение 2.
template <bool check>
inline void insert(Holder* holder,Element* element,int index){
do1();
if (check)
holder->ensureRange(index);//a little expensive
do2();
}
Если у вас есть C ++ 17, вам больше не нужно зависеть от компилятора для удаления мертвого кода, так как вы можете принудить его пропускать определенный код через constexpr-if. Эта конструкция гарантирует, что код в операторе if будет удален, поскольку его даже не нужно будет компилировать.
template <bool check>
inline void insert(Holder* holder,Element* element,int index){
do1();
if constexpr (check)
holder->ensureRange(index);//a little expensive
do2();
}
insert(Holder* holder,Element* element,int index, bool ensureRange){
//............ do some complex thing 1
if (ensureRange){
holder->ensureRange(index);//a little expensive
}
//............ do some complex thing 2
}
И если вы можете принять решение во время компиляции и хотите использовать шаблоны:
template<bool check> insert(Holder* holder,Element* element,int index){
//............ do some complex thing 1;
if(check)holder->ensureRange(index);
//............ do some complex thing 2
}insert<true>(...); //expensive operation
insert<false>(...); //cheap operation
Технически, я бы предпочел решение, подобное показанному в ответ мвидельгауза, но добавленные аргументы выглядят плохо, когда функция должна быть вызвана на самом деле из-за непонятных литералов true
или же false
, Поэтому я бы объединил три функции: внутреннюю (private
) функция, которая предоставляет флажок проверки в качестве аргумента, и две внешние функции, которые предоставляют очевидные имена для функции, я бы предпочел что-то вроде checkedInsert
, uncheckedInsert
, Эти публичные функции будут вызывать реализацию с четырьмя аргументами.
В следующем фрагменте кода уменьшено большинство параметров, чтобы сделать решение наиболее очевидным. Это следует рассматривать как фрагмент определения класса:
public:
/// performs a fast insert without range checking. Range-check yourself before!
void checkedInsert(Element* element)
{
insertWithCheckOption(element, true);
}
/// performs a range-checked insert
void uncheckedInsert(Element* element)
{
insertWithCheckOption(element, false);
}
private:
/// implements insert with range check option
void insertWithCheckOption(Element* element, bool doRangeCheck)
{
// ... do before code portion ...
if (doRangeCheck) {
// do expansive range checking ...
}
// ... do after code portion ...
}
Это решение обеспечивает как лучший пользовательский интерфейс, так и последовательную реализацию.
КСТАТИ:
Я действительно сомневаюсь, что реальные проблемы с производительностью будут вызваны одной условной проверкой.
Предположим, вы создали следующую структуру:
Класс, который определяет два защищенных метода do1
а также do2
и публичный абстрактный метод Insert
,
class BaseHolder
{
proteted:
void do1(/* complete with necessary parameters*/)
{
}
void do2(/* complete with necessary parameters*/)
{
}
public:
abstract void Insert(Holder* holder, Element* element, int index);
};
class Expensive : BaseHolder
{
public:
void Insert(Holder* holder, Element* element, int index)
{
do1();
holder->ensureRange(index);
do2();
}
};
class Cheap : BaseHolder
{
public:
void Insert(Holder* holder, Element* element, int index)
{
do1();
do2();
}
};
Извините, если я допустил некоторые синтаксические ошибки, но так я вижу решение.
Другая возможность — сделать заказ Cheap
а также Expensive
классы, которые оба обернуть Holder
и в Expensive
конструктор делает проверку для диапазона:
class Base
{
protected:
Holder* _holder;
public:
Holder* GetHolder(){ return _holder; }
}
class Cheap : Base
{
public:
Cheap(Holder* holder)
{
_holder = holder;
}
};
class Expensive : Base
{
public:
Expensive(Holder* holder)
{
holder->ensureRange(index);
_holder = holder;
}
};
Использование дешевых и дорогих объектов в качестве параметров для Insert
метод.
Я думаю, что второе решение лучше, чем первое.
И решение больше похоже на первое, но в котором используется шаблон проектирования метода шаблона, поскольку лучшее приходит в конце:
class BaseHolder
{
proteted:
void do1(/* complete with necessary parameters*/)
{
}
void do2(/* complete with necessary parameters*/)
{
}
virtual void check(Holder* holder, int index){ };
public:
void Insert(Holder* holder, Element* element, int index)
{
do1();
check(holder, index);
do2();
};
class Expensive : BaseHolder
{
protected:
override void check(Holder* holder, int index)
{
holder->ensureRange(index);
}
};
class Cheap : BaseHolder
{
};
Это решение определяет check
метод только на Expensive
объект, он имеет наибольшее количество повторного использования кода, и это, безусловно, один из самых чистых подходов. К сожалению, речь идет не о производительности, а о производительности, где она не самая лучшая, но вам придется подумать, что является вашим приоритетом, как вы только что пришли к выводу.
В качестве другой альтернативы вы можете просто использовать аргумент функции по умолчанию со значением по умолчанию, соответствующим отсутствию проверки диапазона
void insert(Holder* holder, Element* element, int index,
bool performRangeCheck = false)
{
/* do some complex thing 1 */
if(performRangeCheck)
{
holder->ensureRange(index);
}
/* do some complex thing 2 */
}
// ...
insert(holder, element, 2); // no range check
insert(holder, element, 2, true); // perform range check