Я программирую очень простую структуру модульного тестирования на С++, которая использует операторы try-catch для анализа переменных. В этом примере у меня есть класс под названием UnitTest с одной общедоступной и одной частной функцией-членом с названиями scalars_are_equal() и is_equal() соответственно. Пользователь должен передать str публичной функции, которая содержит строку с названием теста. Пользователь также должен передать value1 и value2 общедоступной функции, которая содержит два скалярных значения для сравнения друг с другом. Публичная функция использует оператор try и передает данные частной функции, где две переменные оцениваются, чтобы увидеть, совпадают ли они. Если значения совпадают, он возвращается к вызывающей функции, где на экран выводится сообщение, чтобы пользователь знал, что тест пройден. Если значения не равны, то приватная функция должна генерировать исключение, назначенное строке msg, а публичная функция должна перехватывать это исключение. Класс прикреплен ниже. Функции написаны в виде шаблонной функции, поэтому пользователь может выбирать для сравнения целые числа, числа с плавающей запятой и удвоения, даже если арифметика с плавающей запятой может означать, что две версии числа не совсем идентичны.
class UnitTest
{
public:
template <class type1, class type2>
void scalars_are_equal(std::string str, const type1 &value1, const type2 &value2);
private:
template <class type1, class type2>
void is_equal(const type1 &value1, const type2 &value2, std::string str);
};
// ================================================================
// ================================================================
// UnitTest PUBLIC member functions
template <class type1, class type2>
void UnitTest::scalars_are_equal(std::string str, const type1 &value1,
const type2 &value2)
{
unsigned long remain;
remain = 60 - str.length();
try {
is_equal(value1, value2, str);
std::cout << str + std::string(remain, '.') +
std::string("PASSED") << std::endl;
}
catch (const char* msg) {
std::cout << msg << std::endl;
}
}
// ----------------------------------------------------------------
template <class type1, class type2>
void UnitTest::is_equal(const type1 &value1, const type2 &value2,
std::string str)
{
if (value1 != value2) {
unsigned long remain;
remain = 60 - str.length();
std::string msg = str + std::string(remain, '.') + " FAILED";
throw msg;
}
}
В этом случае основная программа выглядит так;
#include <iostream>
#include <string>
#include <vector>
#include <array>
#include "unit_test.hpp"
int main(int argc, const char * argv[]) {
UnitTest q;
{ // Test to see if code catches unequal scalars
float a, b;
a = 20.0;
b = 30.0;
std::string c ("Scalars_Unequal_Test");
q.scalars_are_equal(c, a, b);
}
По непонятным мне причинам оператор catch в функции scalars_are_equal() не перехватывает функцию is_equal(). Сначала я подумал, что это может быть из-за того, что функция выдает std::string, но когда я изменил оператор catch с const char на std::string, это не имело значения. Кто-нибудь знает, почему это не ловит исключение?
Способен воспроизвести const char *, но это ожидаемое поведение. Не удалось воспроизвести версию std::string. Изменения, которые мне пришлось внести в код для его компиляции, могли что-то изменить. Можем ли мы получить минимальный воспроизводимый пример, чтобы попробовать самостоятельно?
В какой-то несвязанной заметке: на самом деле это не то, для чего нужен try and catch. Try and catch предназначен для обнаружения и устранения ошибок, а не для передачи данных туда и обратно, что, похоже, вы и делаете.
@Chipster Я бы сказал, что создание исключения для сбоя модульного теста вполне разумно, так работают многие среды модульного тестирования.





Вы бросаете std::string, а не char* в UnitTest::is_equal().
std::string msg = str + std::string(remain, '.') + " FAILED";
throw msg;
Итак, вам нужно поймать строку:
catch (std::string& msg) {
std::cout << msg << std::endl;
}
Важное примечание: не выбрасывайте std::string или подобные, а классы, производные от std::exception или вашего собственного базового класса исключений. Например:
std::string msg = str + std::string(remain, '.') + " FAILED";
throw std::runtime_error(msg);
[...]
catch (std::runtime_error& e) {
std::cout << e.what() << std::endl;
}
Тип вашего улова должен быть таким же, как тип, который вы выбрасываете (или родительский класс этого типа).
Ваш код по существу сводится к
try
{
throw std::string( "test" );
}
catch ( const char* msg )
{
}
Вы выбрасываете std::string, но ловите const char*, поэтому исключение не перехватывается. Вам нужно либо бросить "test" напрямую, не оборачивая его в std::string, либо изменить улов на std::string&.
Выброс любого типа указателя приводит к проблемам с владением, кто несет ответственность за освобождение указателя? Например, в вашем коде, если вы «исправили» проблему, вызвав throw msg.c_str(), строка msg будет уничтожена до того, как будет достигнут ваш блок catch, а указатель char* будет недействительным, чтобы исправить это, вам придется динамически выделить новый массив char и скопируйте msg в него, тогда блоку catch потребуется delete массив, но он не может знать, что ему это нужно.
Генерация произвольных типов разрешена языком, но рекомендуется получить все ваши типы бросков из std::exception, что позволит вам написать код, подобный следующему, и иметь возможность захватывать все исключения, генерируемые вашим кодом.
try
{
// lots of code including calling third party libraries
}
catch ( std::exception& ex )
{
std::cout << "uncaught exception: " << ex.what() << "\n";
}
Типы исключений обычно перехватываются по ссылке, поскольку это позволяет избежать их копирования в контекст перехвата, а также позволяет избежать проблем нарезки с производными классами.
Вам действительно не нужен весь этот код для показать проблему. Подсказка:
std::stringнеconst char *.