Допустим, у меня есть статический метод, который сравнивает два объекта для близкого соответствия и возвращает некоторый уровень достоверности [0,1].
class Foo
{
...
static double Compare(const Foo& foo1, const Foo& foo2);
...
};
Теперь мне нужно вернуть дополнительную отладочную информацию, содержащую детали сравнения, в зависимости от настройки в конфигурации.
Так как эта отладочная информация не будет использоваться на производстве, а только в целях тестирования / отладки, мне было интересно, как правильно ее реализовать.
Я вижу как минимум три варианта:
1: Создайте дополнительный класс CompareResult и сохраните там доверие + дополнительную информацию. Не заполняйте дополнительную информацию, если вам не нужно.
class CompareResult
{
...
private:
double confidence_;
CompareOptionalInfo compare_optional_info_;
...
};
...
static CompareResult Compare(const Foo& foo1, const Foo& foo2);
Кажется, он самый чистый, но я не уверен, стоит ли мне комбинировать возвращаемый результат с дополнительной информацией.
2: Используйте выходную переменную (таким образом, нам не нужно создавать дополнительный класс, но сигнатура нашего метода немного вырастет)
static double Compare(const Foo& foo1, const Foo& foo2, CompareOptionalInfo* out_compare_info = nullptr);
3: Отдельный метод сравнения с методом извлечения необязательной информации.
static double Compare(const Foo& foo1, const Foo& foo2);
static CompareOptionalInfo GetCompareOptionalInfo();
Эта опция, вероятно, потребует сохранения этой необязательной информации между вызовами метода и переходом от статического метода сравнения к методу сравнения экземпляра.
Но опять же, я не уверен, подходит ли это или нет.
Исходя из вашего опыта, каков подходящий способ в мире ООП возвращать необязательную информацию (которая будет в основном использоваться только в режиме отладки) из метода?
Вариант 3 это не очень хорошая идея: наличие функций, зависящих от статических данных, не практично и может даже стать источником ошибок при отладке. Такая конструкция, кроме того, не является поточно-ориентированной; Как жаль, что такое ограничение будет создано только для целей отладки!
Примеры проблем:
double closest = std::max (Foo::compare (x, y), Foo::compare (y,z));
clog << Foo::GetCompareOptionalInfo(); // undefined which info since order of eval
// of function parameter is not guaranteed
double d = Foo::compare (x, y);
DoSomething(); // what if this one does an unexpected compare ?
clog << Foo::GetCompareOptionalInfo();
Вариант 2 Это жизнеспособное решение, но оно не очень удобно: оно заставляет вас создавать информационные объекты, передавать их по адресу и т. д .:
Foo::OptionalCompareInfo o1,o2; // cumbersome
double closest = std::max (Foo::compare (x, y, &o1), Foo::compare (y,z, &o2));
Кроме того, вы должны создать эту необязательную информацию и передать дополнительный аргумент в производство, даже если вы больше не обновляете там информацию (если вы не добавили много дополнительной условной компиляции)!
Опция 1 отлично! Действуй ! Это действительно использует преимущества парадигмы ООП и использует чистый дизайн. Это практично и не накладывает ограничений на ваш код для его использования.
Все, что вам нужно, это предоставить некоторую (неявную) функцию преобразования для использования вашего CompareResult
почти как если бы это было double
:
class CompareResult
{
public:
CompareResult(double d=0.0) : confidence_(d) {};
operator double() { return confidence_; }
operator bool() { return confidence_>0.5; }
private:
double confidence_;
CompareOptionalInfo compare_optional_info_;
};
Ваш производственный код не будет затронут отладочной информацией. И вы можете в своей отладке ВСЕГДА отследить объяснение любого результата сравнения, по крайней мере, если вы сохраните его:
Пример:
auto result = Foo::compare (x, y)
if (result) ... // uses automatically the conversion
auto closest = std::max (Foo::compare (x, y), Foo::compare (y,z));
// here you not only know the closest match but could explain it !
vector<CompareResult> v;
... // populate the vector with 10 000 comparison results
auto r = std::max_element(v.begin(), v.end());
// you could still explain the compare info for the largest value
// if you're interested in the details of this single value
// how would you do this with option 3 or option 2 ?
Хорошо, чтобы последний работал, вам также понадобится оператор сравнения для вашего дополнительного класса. Но это еще одна строка кода (см. Онлайн-демонстрацию) 😉
Наконец, может оказаться, что ваша «дополнительная информация отладки» может оказаться более полезной, чем ожидалось, например, предоставить пользователю дополнительные объяснения по запросу. Все, что вам нужно сделать, это удалить условные #ifdef DEBUG
вокруг расчета дополнительной информации.
Я бы использовал второй вариант для его совместимости с отладчиками.
В режиме отладки вы можете добавить дополнительный статический член. Вам следует позаботиться о компоновщике, который не должен его подавлять.
class Foo
{
private:
#ifndef NDEBUG
CompareOptionalInfo debug_out_compare_info_;
#endif
...
static double Compare(const Foo& foo1, const Foo& foo2,
CompareOptionalInfo* out_compare_info = nullptr);
...
};
#ifndef NDEBUG
CompareOptionalInfo Foo::debug_out_compare_info_;
#endif
В gdb в любой точке останова вы можете использовать:
call Foo::Compare(foo1, foo2, &Foo::debug_out_compare_info_);
print Foo::debug_out_compare_info_. ...