Rewritable dynamically-typed value as a class member
(This is sort of a long-winded question but I have summarized it at the bottom.)
I want to write a class (in C++) that performs tests on objects of some unknown type deriving from a very skeletal base class. The idea is that an object of this class is initialized with an "expected" result and then called many times, saving the outcome and comparing it to the expected one. The whole package should look something like this:
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;
}
My intention is that the same object of type test
can be called repeatedly on many test_function
s, which is why its member result
must be a pointer rather t开发者_运维知识库han a reference: I can't reassign a reference.
The problem is that the code for operator()
is wrong: the left-hand side is not a reference to a test_output
, so it is statically cast to the exact class test_output
, which is purely virtual; however, I want operator==
to be dynamically bound to the equality operator for type to_derived
. As is, it will try to downgrade expected
to the base type test_output
and complain that I don't have such an operator (and if I did, it would be the wrong one anyway). Note: switching the order of the operands would cause operator==
to be that of to_derived
, but then the compiler would complain that the type of the second argument was wrong.
I guess I could make test
a template depending on types, say template <typename I, typename O>
, replacing test_input
and test_output
in its code, but that is not so good because it fails to specify that I
and O
inherit the virtual functions I want (which is why I have those two classes in the first place).
It's tempting, though sort of inelegant, to want to overload operator==
on type test_output*
, but that's not legal, is it? I could make only one of the arguments a pointer, since expected
can remain a reference type, but again: inelegant. This is not how I would write the code if I did not need result
to be reassignable, and so it is not how I want to write the code.
If this were not in a class, I could just define a new reference variable every time I want to save a new result
, but I can only have so many members. Syntactically, this sounds like a situation where I'd want a "pointer to a reference to test_output
", but that's illegal also. (Either that, or something like a "rebind
" operator for references.) A test_output **
is no good, since I want to ultimately be able to pass objects of (base) type test_output
; if I do one dereference I just get a test_output *
, but if I do two then the type is no longer dynamic.
So how do I do this? Specifically, I want:
to bind the equivalent of
operator==(*result, expected)
to the equality operator for the dynamic types of these things;to be able to indicate to the compiler that those types are derived from
test_output
;to be able to reassign
result
.
I am also wondering: in my sample usage, is it valid to use those function pointers as arguments to test_object
? Their arguments can be dynamically typed but I don't know whether that means the function type itself has an automatic conversion to the function type taking and returning the respective base types. If not, how would I indicate a function of this dynamically-typed signature?
Sorry, but the design seems to be flawed in several aspects. First,
typedef test_output& (*test_function)(test_input&);
calls for trouble. Don't return references to objects generated inside a function. The referenced object is destroyed, when the called function ends, so you will store in result
the address of an object that does not exist anymore (dangling pointer). If you need polymorphic results, return them safely as shared_ptr<test_output>
, which should also be the type of result
.
Concerning your list of questions (sorry for long answer):
to bind the equivalent of operator==(*result, expected) to the equality operator for the dynamic types of these things;
Yes, as you did. The equality test should be overridden for every class derived from test_output
.
to be able to indicate to the compiler that those types are derived from test_output;
For comparing polymorphic objects for the left operand with polymorphic objects on the right hand side, welcome to the field of multi-methods. The problem boils down to: "When do you consider two objects as equal?" Should they have exactly the same type or can one belong to a subset of the other? Normally the Liskov substitution principle in object orientation means: A derived object can be substituted for a base object, since inheritance guarantees it has all base properties.
To determine if expected
to be of (at least) to_derived
you need a 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;
}
If you want to guarantee expected
to be of exactly the same type, you should use RTTI first:
if (typeid(*this) != typeid(expected)) return false;
Derived objects are no consired of the same type. Maybe your compiler needs a switch to enable that.
to be able to reassign result.
Use a shared_ptr<test_output>
, because it manages memory for dynamic objects.
is it valid to use those function pointers as arguments to test_object? Their arguments can be dynamically typed but I don't know whether that means the function type itself has an automatic conversion to the function type taking and returning the respective base types. If not, how would I indicate a function of this dynamically-typed signature?
The function pointers does not need any conversion. But your test framework seems to be incomplete, since it may work only for standalone functions. What about methods? I would suggest a templated solution.
I would seriously consider looking for an existing unit testing framework.
Can't you change,
{ return *(result = &trial(data)) == expected; }
To simply,
{ return trial(data) == expected; }
That should call a proper virtual bool operator == ()
. Do you really need result
? From your code, I don't feel you need it.
Edit: If you want result
for diagnostics then make it a bool
:
bool result; // = false (default)
//...
{ return result = (trial(data) == expected); }
For some purpose if you still want &trial()
function address, then you can store a function pointer to that as well. So ultimately you are introducing just an extra bool
variable.
精彩评论