(这是一个冗长的问题,但我已在底部对其进行了总结。)
我想编写一个类(在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
必须是指针而不是引用:I无法重新分配参考资料。
问题是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
的参数是否有效?它们的参数可以动态输入,但我不知道这是否意味着函数类型本身具有自动转换为函数类型并返回相应的基类型。如果没有,我如何指出这种动态类型签名的功能?
答案 0 :(得分:1)
很抱歉,但设计似乎在几个方面存在缺陷。首先,
typedef test_output& (*test_function)(test_input&);
要求麻烦。不要返回对函数内生成的对象的引用。当被调用函数结束时,被引用的对象被销毁,因此您将在result
中存储不再存在的对象的地址(悬空指针)。如果您需要多态结果,请将其安全地返回shared_ptr<test_output>
,这也应该是result
的类型。
关于你的问题清单(对不起,答案很长):
将等效的operator ==(* result,expected)绑定到相等运算符以获取这些事物的动态类型;
是的,就像你做的那样。对于从test_output
派生的每个类,都应该重写相等测试。
能够向编译器指示那些类型是从test_output派生的;
为了比较左操作数的多态对象和右侧的多态对象,欢迎使用多方法字段。问题归结为:“你什么时候认为两个对象是平等的?”它们应该具有完全相同的类型还是属于另一个的子集?通常,面向对象中的 Liskov替换原则意味着:派生对象可以替换基础对象,因为继承保证它具有所有基本属性。
要确定expected
是否属于(至少)to_derived
,您需要向下投射:
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:
if (typeid(*this) != typeid(expected)) return false;
派生对象不属于同一类型。也许您的编译器需要一个开关才能启用它。
能够重新分配结果。
使用shared_ptr<test_output>
,因为它管理动态对象的内存。
将这些函数指针用作test_object的参数是否有效?它们的参数可以动态输入,但我不知道这是否意味着函数类型本身具有自动转换为函数类型并返回相应的基类型。如果没有,我如何指出这种动态类型签名的功能?
函数指针不需要任何转换。 但是您的测试框架似乎不完整,因为它可能仅适用于独立功能。方法怎么样?我建议一个模板化的解决方案。
我会认真考虑寻找existing unit testing framework。
答案 1 :(得分:0)
你不能改变,
{ 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
变量。