Перезаписываемое динамически типизированное значение в качестве члена класса - PullRequest
1 голос
/ 16 июля 2011

(Это довольно скучный вопрос, но я суммировал его внизу.)

Я хочу написать класс (на C ++), который выполняет тесты для объектов какого-то неизвестного типа, происходящих из очень скелетного базового класса. Идея состоит в том, что объект этого класса инициализируется с «ожидаемым» результатом, а затем вызывается много раз, сохраняя результат и сравнивая его с ожидаемым. Вся упаковка должна выглядеть примерно так:

struct test_input
{
  virtual ~test_input() = 0;
};

struct test_output
{
  virtual bool operator== (const test_output&) = 0;
  virtual ~test_output() = 0;
};

typedef test_output& (*test_function)(test_input&);

class test
{
  const test_input &data;
  const test_output &expected;
  test_output *result;

  test(test_input &i, test_output &o)
    : data(i), expected(o), result(NULL)
    {}

  bool operator() (test_function &trial)
    { return *(result = &trial(data)) == expected; }
};

// Example usage
class ti_derived : public test_input { /* ... */ };
class to_derived : public test_output { /* ... */ };
to_derived& some_function_one(ti_derived &arg) { /* ... */ }
to_derived& some_function_two(ti_derived &arg) { /* ... */ }

ti_derived ti; // Somehow initialized
to_derived correct; // Somehow determined
test test_object(ti, correct);
if (!test_object(&some_function_one)) {
  cout << "1: " << test_object.result;
}
if (!test_object(&some_function_two)) {
  cout << "2: " << test_object.result;
}    

Я предполагаю, что один и тот же объект типа test может вызываться многократно на многих test_function с, поэтому его член result должен быть указателем, а не ссылкой: я не могу переназначить ссылку .

Проблема в том, что код для operator() неверен: левая часть не является ссылкой на test_output, поэтому он статически приводится к точному классу test_output, который является чисто виртуальным; однако я хочу, чтобы operator== был динамически связан с оператором равенства для типа to_derived. Таким образом, он попытается понизить expected до базового типа test_output и пожаловаться на то, что у меня нет такого оператора (и если бы я это сделал, он все равно был бы неправильным). Примечание: переключение порядка операндов приведет к тому, что operator== будет равным to_derived, но тогда компилятор будет жаловаться на неправильный тип второго аргумента.

Полагаю, я мог бы сделать test шаблоном в зависимости от типов, скажем, template <typename I, typename O>, заменив test_input и test_output в своем коде, но это не так хорошо, потому что он не может указать, что I O наследуют виртуальные функции, которые я хочу (именно поэтому у меня есть эти два класса в первую очередь).

Заманчиво, хотя и не совсем элегантно, хотеть перегрузить operator== для типа test_output*, но это не законно, не так ли? Я мог бы сделать только один из аргументов указателем, поскольку expected может оставаться ссылочным типом, но опять же: неэлегантный. Это не то, как я написал бы код, если бы мне не нужно было result переназначаться, и поэтому я не хочу писать код.

Если бы этого не было в классе, я мог бы просто определить новую ссылочную переменную каждый раз, когда я хочу сохранить новый result, но у меня может быть только так много членов. Синтаксически, это звучит как ситуация, когда я бы хотел "указатель на ссылку на test_output", но это также незаконно. (Либо это, либо что-то вроде оператора "rebind" для ссылок.) test_output ** не годится, так как я хочу, в конечном счете, иметь возможность передавать объекты (базового) типа test_output; если я делаю одну разыменование, я просто получаю test_output *, но если я делаю два, то тип больше не является динамическим.

Так как мне это сделать? В частности, я хочу:

  • , чтобы связать эквивалент operator==(*result, expected) с оператором равенства для динамических типов этих вещей;

  • , чтобы иметь возможность указать компилятору, что эти типы являются производными от test_output;

  • для возможности переназначения result.

Мне также интересно: допустимо ли в моем примере использование этих указателей на функции в качестве аргументов для test_object? Их аргументы могут быть динамически типизированы, но я не знаю, означает ли это, что сам тип функции имеет автоматическое преобразование в тип функции, принимающий и возвращающий соответствующие базовые типы. Если нет, то как бы я указал функцию этой динамически типизированной подписи?

Ответы [ 2 ]

1 голос
/ 16 июля 2011

Извините, но дизайн кажется некорректным в нескольких аспектах.Во-первых,

typedef test_output& (*test_function)(test_input&);

требует неприятностей.Не возвращайте ссылки на объекты, созданные внутри функции.Указанный объект уничтожается, когда вызываемая функция завершается, поэтому вы сохраните в result адрес объекта, который больше не существует (висящий указатель).Если вам нужны полиморфные результаты, верните их безопасно как shared_ptr<test_output>, что также должно быть типа result.

относительно вашего списка вопросов (извините за длинный ответ):

чтобы связать эквивалент оператора == (* ожидаемый результат) с оператором равенства для динамических типов этих вещей;

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

, чтобы иметь возможность указать компилятору, что эти типы получены из test_output;

Длясравнивая полиморфные объекты для левого операнда с полиморфными объектами на правой стороне, добро пожаловать в поле multi-методов .Проблема сводится к следующему: «Когда вы считаете два объекта равными?»Должны ли они иметь один и тот же тип или один может принадлежать подмножеству другого?Обычно принцип подстановки Лискова в ориентации объекта означает: производный объект может быть заменен базовым объектом, поскольку наследование гарантирует, что он обладает всеми базовыми свойствами.Чтобы определить, является ли expected (по крайней мере) to_derived, вам необходимо downcast :

bool to_derived::operator==(const test_output& expected)
{
  if (to_derived* e = dynamic_cast<to_derived*>(&expected))
  {
    // compare all data fields, then
    return true; 
  }
  return false;
}

Если вы хотите, чтобы expected было ровно того же типа, вы должны использовать RTTI first:

  if (typeid(*this) != typeid(expected)) return false;

Производные объекты не состоят из одного и того же типа.Возможно, вашему компилятору нужен переключатель, чтобы включить это.

, чтобы иметь возможность переназначить результат.

Используйте shared_ptr<test_output>, потому что он управляет памятью для динамических объектов.

допустимо ли использовать эти указатели на функции в качестве аргументов для test_object?Их аргументы могут быть динамически типизированы, но я не знаю, означает ли это, что сам тип функции имеет автоматическое преобразование в тип функции, принимающий и возвращающий соответствующие базовые типы.Если нет, то как бы я обозначил функцию этой динамически типизированной подписи?

Указатели на функции не нуждаются в преобразовании.Но ваш тестовый фреймворк кажется неполным, поскольку он может работать только для автономных функций.Как насчет методов?Я бы предложил шаблонное решение.

Я бы серьезно подумал о поиске существующей платформы модульного тестирования .

0 голосов
/ 16 июля 2011

Не можете ли вы изменить,

{ return *(result = &trial(data)) == expected; }

Чтобы просто,

{ return trial(data) == expected; }

Это должно назвать правильное virtual bool operator == (). Вам действительно нужно result? Из вашего кода я не чувствую, что вам это нужно.

Редактировать : Если вы хотите result для диагностики, сделайте его bool:

bool result;  // = false (default)
//...
{ return result = (trial(data) == expected); }

Для каких-то целей, если вы все еще хотите &trial() адрес функции, вы также можете сохранить указатель на эту функцию. Итак, в конечном итоге вы вводите просто дополнительную bool переменную.

...