我有一个执行测试用例的C ++应用程序。某些测试用例可能依赖于其他测试用例的输出。
所有测试用例都实现了一个基本界面:
/// base class for all test cases
class ITest
{
public:
virtual void Execute() = 0;
};
生成一些可能对其他测试用例有用的对象的测试用例实现了这个接口:
/// implemented by test cases that provide data to other test cases
template< class Obj >
class IDependency
{
public:
virtual Obj Get() = 0;
};
需要来自其他测试用例的数据的测试用例实现此接口:
/// implemented by test cases that require data from other test cases
template< class Obj >
class IDependent
{
public:
void SetDependency( IDependency< Obj >* dependency )
{
dependency_ = dependency;
};
protected:
Obj GetDependency() const
{
return dependency_->Get();
};
private:
IDependency< Obj >* dependency_;
};
两个示例测试用例。一个需要const wchar_t
个对象;一个产生该对象:
/// A test case that provides a "const wchar_t*" object to other test cases
class Foo : public ITest,
public IDependency< const wchar_t* >
{
public:
const wchar_t* Get()
{
if( object_.length() == 0 )
Execute();
return object_.c_str();
};
virtual void Execute()
{
printf( "Execute Foo\n" );
object_ = L"Object produced by Foo";
};
private:
std::wstring object_;
};
/// A test case that first requires a "const wchar_t*" object
class Bar : public ITest,
public IDependent< const wchar_t* >
{
public:
virtual void Execute()
{
const wchar_t* needed_object = GetDependency();
printf( "Execute Bar with %S\n", needed_object );
};
};
测试用例存储在列表中。通过注册过程将案例添加到列表中:
/// List of test cases to execute
std::vector< ITest* > list_;
/// Register a test case to execute with the system
void Register( ITest* test_case )
{
list_.push_back( test_case );
}
这是我的问题。我想实现'Register()'函数的重载,该函数也接受依赖项。但是,因为依赖关系可以是任何类型(不仅仅是这个例子中的'const wchar_t *'),我不知道如何管理它。以下是我正在寻找的或多或少的示例,但我不确定如何使其工作。
/// Register a test case with dependencies with the system
void Register( ITest* test_case, ITest* dependency, ... )
{
IDependent< ??? >* dependent = dynamic_cast< IDependent< ??? >* >( test_case );
IDependency< ??? >* dep = dynamic_cast< IDependency< ??? >* >( dependency );
va_list dep_list;
for( va_start( dep_list, dependency );
NULL != dep;
dep = dynamic_cast< IDependency< ??? >* >( va_arg( dep_list, ITest* ) ) )
{
dependent->SetDependency( dep );
}
va_end( dep_list );
Register( test_case );
}
示例用法:
int _tmain( int argc, _TCHAR* argv[] )
{
/// Test case Foo
Foo foo;
/// Test case bar (depends on Foo)
Bar bar;
/// Register test case Bar with a dependency on Foo
Register( &bar, &foo );
/// Execute Bar. Because it depends on Foo, that will be executed first
list_->begin()->Execute();
return 0;
}
预期产出:
Execute Foo
Execute Bar with Object produced by Foo
有没有人对如何成功实施这种架构有任何建议? (或者更好的架构实际上有效吗?)
谢谢, PaulH
答案 0 :(得分:7)
我看到两种可能的解决方案。
使Register()方法成为模板。 简单的解决方案是将依赖项的数量限制在一些合理的最大值。
template <class T, class D1>
void Register(T* test_case, IDependency<D1>* d1)
{
BOOST_STATIC_ASSERT(boost::is_base_and_derived<IDependent<D1>, T>::value);
// since we now know that T is a IDependent<D1>, a dynamic_cast would only be necessary
// to allow virtual inheritance.
static_cast<IDependent<D1>*>(test_case)->SetDependency(d1);
Register(test_case);
}
template <class T, class D1, class D2>
void Register(T* test_case, IDependency<D1>* d1, IDependency<D2>* d2)
{
BOOST_STATIC_ASSERT(boost::is_base_and_derived<IDependent<D1>, T>::value);
static_cast<IDependent<D1>*>(test_case)->SetDependency(d1);
Register(test_case, d2);
}
template <class T, class D1, class D2, class D3>
void Register(T* test_case, IDependency<D1>* d1, IDependency<D2>* d2, IDependency<D3>* d3)
{
BOOST_STATIC_ASSERT(boost::is_base_and_derived<IDependent<D1>, T>::value);
static_cast<IDependent<D1>*>(test_case)->SetDependency(d1);
Register(test_case, d2, d3);
}
// ...
对于支持可变参数模板的编译器,这可能只能在一个函数模板中编写,无限量的依赖项。
或者你可以让Register()返回一个代理类,这样就可以这样写:
Register(test_case)(dep1)(dep2)(dep3) /* ... */ (depN);
代理类将存储指向容器和测试用例的指针,并定义一个函数调用操作符,它看起来就像上面示例中的Register(T * test_case,IDependency * d1)函数一样,只是没有“test_case”参数和对Register(test_case)的最终调用(可以在代理类的dtor中执行)。
如果我理解你正在尝试做什么,每个“依赖”只能产生一种结果。 在这种情况下,您可以像这样修改IDependency接口:
class IDependencyBase
{
public:
virtual void ApplyTo(ITest* target) = 0;
};
template <class T>
class IDependency : public IDependencyBase
{
public:
virtual void ApplyTo(ITest* target)
{
// cast to reference gives us an std::bad_cast if the types are not compatible,
// which I think is a good thing here
dynamic_cast<IDependancy<T>&>(*target).SetDependancy(this);
}
virtual T Get() = 0;
};
template <class InputIterator>
void Register(ITest* test_case, InputIterator begin, InputIterator end)
{
for (; begin != end; ++begin)
{
IDependancyBase* dep = *begin;
dep->ApplyTo(test_case);
}
Register(test_case);
}
template <class Container>
void Register(ITest* test_case, Container deps)
{
Register(test_case, deps.begin(), deps.end());
}
现在再次实施varargs解决方案似乎很诱人,就像这样(从第二个例子开始):
void Register(ITest* test_case, ...)
{
va_list dep_list;
va_start(dep_list, test_case);
while(IDependencyBase* dep = va_arg(dep_list, ITest*))
dep->ApplyTo(test_case);
va_end(dep_list);
Register( test_case );
}
// and use it like
int _tmain( int argc, _TCHAR* argv[] )
{
Foo foo;
Bar bar;
Register(&foo);
Register(&bar, &foo, 0);
list_->begin()->Execute();
return 0;
}
然而,这并不能保证有效。在上面的代码中,Foo *存储为varagrs参数,并作为IDependencyBase *读回。这不能保证工作,因为Foo和IDependencyBase都不是POD(IIRC它们都必须是POD才能保证工作 - 也许它不能保证,即使那时,我也必须在标准中查找) 。这不是一些遥不可及的“不能由标准保证,但可以在任何地方工作”的事情。引入多重和/或虚拟继承,这几乎可以保证失败。
使用C ++时的一般建议:除非没有其他方法,否则不要使用varargs函数。并且总有另一种方式。
答案 1 :(得分:3)
OOG。棘手的问题。好的,首先,我将尝试解释为什么您在这里遇到的模板方法导致问题;然后我会尝试提供另一种解决方案。
到此为止。
模板是下一代宏的发展。它们具有更多类型安全性,但它们仍有一些限制,当您尝试链接到模板对象时,这些限制变得非常明显。它变得凌乱。这里的关键是,当你声明类时:
template< class Obj > class IDependency
实际上并未创建或定义任何内容。此处(尚未)定义了类,具体而言,没有(并且永远不会是)名为IDependency
的实际可用类。
当您尝试使用此模板类时,然后编译器将实例化模板,生成一个实际的,已定义的类(有一些具体名称在封面下发生。即当你说IDependency< const wchar_t* >
时,编译器会为这个模板的const wchar_t*
风格生成一个类定义,你将在幕后有一个类似IDependency_const_wchart_p
的类。
这可能听起来像是一些乏味的低级细节,但这里有一个非常重要的原因:IDependency<const wchar_t*>
和IDependency<int>
绝对没有任何共同点。它们不是同一类,它们没有共同的基类型。
如果您想处理通用IDependency
,则会出现问题。这意味着您必须使用它的特定实例,或者制作通用模板方法 - 但这只会推迟不可避免的 - 为了实际使用您的模板方法,您必须使用特定的实例化模板。
这有点啰嗦,但这意味着如果你想动态地转换为IDependency,你就不能。您只能动态转换为IDependency的实例化。
例如:这是合法的:
IDependent< const wchar_t* >* dependent =
dynamic_cast< IDependent< const wchar_t* >* >( test_case );
if (dependent) {
IDependency< const wchar_t* >* dep =
dynamic_cast< IDependency< const wchar_t* >* >( dependency );
}
else
{
IDependent< const int >* dependent =
dynamic_cast<IDependent<const int>* (test_case);
if (dependent) {
...
}
}
显然,这不太理想。您可以通过将其拉入模板方法(例如try_to_attach_dependency<T>
),然后使用不同类型重复调用它直到成功来稍微清理它。
其他问题:您正在尝试将一堆IDependency对象附加到您的IDependent对象 - 这要求所有IDependency对象具有相同的模板特化(即您不能混合&lt; int&gt; ;与&lt; wchar_t&gt;)。您可以在运行时检查这个(因为我们正在进行动态转换)并忽略不同的,或者如果IDependency对象不是同类的则抛出错误。但是你无法在编译时强制执行此操作。这就是dynamic_cast的危险。
那么,你能做些什么呢?我想这是你真正关心的部分。
您有几个选择:
您可以创建 IDependency的实际基类。这将允许您传递IDependency *对象,这在一开始似乎是一个好主意,但问题是Get
方法。这是一个模板类的全部原因是,您可以拥有一个返回不同类型的方法,具体取决于正在使用的特化。你不能用基本类型做到这一点(没有一些创造力)。
您可以使用Obj Get()
方法代替void Set(ITest*)
方法。这样,您就不会向ITest询问IDependency的信息,而是让IDependency告诉ITest信息。这样做是一种反转,但它允许您为IDependency类创建一个非模板基类。我不确定你打算如何使用Get,所以这种反转机制可能适合你。
或者,您可以摆脱变量参数列表。 Va_args通常是一个坏主意(没有安全性),通过将其推到调用者的级别,您可以模板化该函数并执行以下操作:
template <typename T>
void Register( ITest* test_case, const std::vector< IDependency<T>* >& dependencies )
{
IDependent<T>* dependent = static_cast< IDependent<T>* >( test_case );
for( std::vector< IDependency<T>* >::const_iterator iter = dependencies.begin();
iter != dependencies.end();
++iter)
{
dependent->SetDependency( *iter );
}
Register( test_case );
}
然后您可以通过执行以下操作来使用此功能:
std::vector< IDependency<const wchar_t*> > dependencies; // or whatever type you want
dependencies.push_back(&foo);
// ... more dependencies as needed
Register(&bar, dependencies);
这会给调用者带来更多的努力(即你不能将它全部转储到以逗号分隔的列表中),但它可以工作,而且更安全。请注意,我们现在可以使用在编译时检查的static_cast
。
此方法确实限制您只使用基于相同类型特化的依赖项。如果您想拥有与类型无关的依赖项,将需要一种非模板方式来保存依赖项。
圣牛,这是一个很长的职位。无论如何,我希望它有所帮助。如果有效,或者您有任何疑问,请告诉我。如果我想到其他任何事情,我会在这里添加。