LINUX.ORG.RU

[c++][рефакторинг] Проверка параметров конструктора.


0

2

Столкнулся со следующей задачей.

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

К данной библиотеке написана привязка для python, через которую, собственно, пользователи и работают.

Функции библиотеки очень активно оперируют объектами описанных выше классов (создают/уничтожают). Поэтому в Release-сборке программы все проверки должны отключаться, оставаясь лишь там, где пользователь явно может вызвать конструктор класса, то есть при вызове конструктора класса из python. В Debug-сборке проверки должны присутствовать везде.

Как можно было бы это реализовать без излишнего дублирования кода и чтобы изменения затрагивали меньший объём кода?


Можно посмотреть в сторону Q_ASSERT из состава Qt. Только не все Qt тащить, а определить самостоятельно подобные маркосы.

trex6 ★★★★★ ()

Первое, что пришло в голову:

Сделать все конструкторы шаблонными, параметр шаблона - булева константа, по умолчанию - false.

Было:

class C
{
  C(X x) { throw_if_wrong(x); }
};

Стало:

class C
{
  template<bool validate = false>
  C(X x) { if(validate) throw_if_wrong(x); }
};

Биндинги придётся переписать, явно указав вызов конструктора с параметром true.

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

Компилировать не пробовал, но на первый взгляд должно проканать :)

Manhunt ★★★★★ ()

Сделай свободные функции для проверки типов. В релизной сборке у них должно быть пустое тело. Вызовы пустых функций компилятор должен удалить.

Absurd ★★★ ()
Ответ на: комментарий от Manhunt

Он хочет, чтобы при вызове из биндинга проверка все-таки производилась.

Для этого надо добавить только один уровень косвенности. То есть в дебажной сборке локальная проверка типов должна дергать биндинговую проверку типов, а в релизной сборке локальная проверка типов должна быть пустой.

Absurd ★★★ ()

Все проверки обернуть в макросы типа assert.

JackyTreehorn ()
Ответ на: комментарий от trex6

Можно посмотреть в сторону Q_ASSERT из состава Qt. Только не все Qt тащить, а определить самостоятельно подобные маркосы.

Пиздец. Вот и выросло поколение...

anonymous ()
Ответ на: комментарий от Absurd

Сделай свободные функции для проверки типов. В релизной сборке у них должно быть пустое тело. Вызовы пустых функций компилятор должен удалить.

Примерно к этому пока и склоняюсь. Но их всё равно придётся вызывать в каждом конструкторе и в обёртке для python. Также в данном случае исчезает наследование проверок (автоматический вызов проверок базового класса при создании субкласса). Так что, либо вызывать все проверки явно, либо вместо свободных функций строить иерархию классов для проверки.

SSN ()
Ответ на: комментарий от Manhunt

При желании можно себя и за локоть укусить. Но в данном конкретном случае шаблоны это совсем-совсем-совсем лишнее... гемора от них будет несоизмеримо больше чем пользы. Говорю как человек, интенсивно скрещивающий плюсы и питон, но у меня просто выбора нету, а у ТСа пока есть;-)

ТС-у - а через что привязка к питону происходит? Можно сделать макрос и навтыкать его там, где нужно.

#ifdef DEBUG
if( !check_arg(arg) ) raise ... ;
#endif

Собираем отладочную версию

g++ ... -DEBUG ...
Собираем релиз
g++ ...
//Ваш К.О.

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

#ifdef DEBUG
virtual bool need_check_arg(){ return true; }
#else
virtual bool need_check_arg(){ return false; }
#endif
а у наследников к -е идут в питон ее переопределить как возвращающую всегда true, ну и уже у нее спрашивать нужна ли проверка.

AIv ★★★★★ ()
Ответ на: комментарий от AIv

ЗЫ а Вы уверены в том, что их вообще надо отключать и что тормоза связаны именно с проверками? Т.е. Вы тестили производительность с проверками и без, выяснили что разница получается кардинальной, причем все проверки вносят существенный вклад и отключать надо именно все проверки, а не каких то две-три? Или это Вам просто кажется - «ой у нас проверок много, наверное они тормозят...» ?;-)

AIv ★★★★★ ()
Ответ на: комментарий от AIv

ЗЫЗЫ. Очепятка, при сборке отладочной версии ес-но

g++ ... -DDEBUG ...

AIv ★★★★★ ()
Ответ на: комментарий от AIv

ЗЫ а Вы уверены в том, что их вообще надо отключать и что тормоза связаны именно с проверками? Т.е. Вы тестили производительность с проверками и без, выяснили что разница получается кардинальной, причем все проверки вносят существенный вклад и отключать надо именно все проверки, а не каких то две-три? Или это Вам просто кажется - «ой у нас проверок много, наверное они тормозят...» ?;-)

Смотрелись результаты под valgrind'ом. К сожалению, часть проверок требует поиска по словарю, что в итоге даёт около 20% от общего времени выполнения функции.

SSN ()
Ответ на: комментарий от AIv

ТС-у - а через что привязка к питону происходит? Можно сделать макрос и навтыкать его там, где нужно.

boost::python

Для таких случаев самый простой вариант - завести у предка виртуальную ф-ю

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

SSN ()
Ответ на: комментарий от SSN

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

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

AIv ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.