I have a C++ application that executes test cases. It is possible that some test cases will depend on output from other test cases.
All test cases implement a basic interface:
/// base class for all test cases
class ITest
{
public:
virtual void Execute() = 0;
};
Test cases that produce some object that may be useful to other test cases implement this interface:
/// implemented by test cases that provide data to other test cases
template< class Obj >
class IDependency
{
public:
virtual Obj Get() = 0;
};
Test cases that require data from other test cases implement this interface:
/// 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_;
};
Two example test cases. One requires a const wchar_t
object; one produces that object:
/// 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 );
};
};
The test cases are stored in a list. Cases are added to the list by a registration process:
/// 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 );
}
Here's my problem. I wanted to implement an overload of the 'Register()' function that also accepts dependencies. But, because dependencies can be of any type (not just the 'const wchar_t*' from this example), I'm not sure how to manage this. Below is an example of more or less what I'm looking for, but I'm not sure how to make it work.
/// 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 );
}
An example usage:
int _tmain(开发者_Go百科 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;
}
Expected output:
Execute Foo
Execute Bar with Object produced by Foo
Does anybody have any suggestions on how I can successfully implement this architecture? (or a better architecture that actually works?)
Thanks, PaulH
I see two possible solutions.
static
Make the Register() method a template. The simple solution would be to limit the number of dependencies to some reasonable maximum.
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);
}
// ...
With compilers that support variadic templates this could probably be written in just one function template, for an unlimited amount of dependencies.
Alternatively you could make Register() return a proxy-class, so that you could write something like this:
Register(test_case)(dep1)(dep2)(dep3) /* ... */ (depN);
The proxy class would store a pointer to the container and the test-case, and define a function-call-operator that looks just like the Register(T* test_case, IDependency* d1) function in the example above, only without the "test_case" argument and the final call to Register(test_case) (which one could do in the dtor of the proxy class).
dynamic
If I understand what you're trying to do correctly, every "Dependency" can only produce one type of result. In that case you could modify the IDependency interface like this:
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());
}
Now it might seem tempting to you to implement your varargs solution again, something like this (continuing from the second example):
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;
}
However that isn't guaranteed to work. In the code above, a Foo* is stored as a varagrs argument, and read back as a IDependencyBase*. That's not guaranteed to work, since neither Foo nor IDependencyBase are PODs (IIRC they would both have to be PODs for this to be guaranteed to work -- maybe it's not guaranteed even then, I'd have to look it up in the standard). This is not some far fetched "not guaranteed by the standatd but will work everywhere" thing. Introduce multiple and/or virtual inheritance and this is almost guaranteed to fail.
So general advice when using C++: don't use varargs functions unless there is no other way. And there is always another way.
Oog. Tough question. Ok, first, I'll try to explain why the template approach you have here is causing problems; and then I'll try to provide an alternate solution.
Here goes.
Templates are sort of the next evolution of macros. They have more type safety, but they still have several limitations, which become painfully obvious when you try to link to template objects. It gets messy. The key thing here, is that when you declare the class:
template< class Obj > class IDependency
Nothing is actually created or defined. There is no class defined here (yet), specifically, there is not (and never will be) an actual, usable class, named IDependency
.
When you try to use this template class, then the compiler will instantiate the template, generating an actual, defined class (with some specific name mangling happening under the covers). I.e. when you say IDependency< const wchar_t* >
the compiler will generate a class definition for the const wchar_t*
flavor of this template, and you'll have a class named something like IDependency_const_wchart_p
behind the scenes.
That may sound like some tedious low-level detail, but it matters here for a very important reason: IDependency<const wchar_t*>
and IDependency<int>
have absolutely nothing in common. They are NOT the same class, and they have NO common base type.
This gives you a problem if you want to handle a generic IDependency
. It means that you MUST use a specific instantiation of it, or craft generic template methods -- but that only postpones the inevitable -- in order to actually use your template methods, you must use a specific instantiation of the template.
That's kinda long winded, but it means that if you want to dynamically cast to an IDependency, you can't. You can only dynamically cast to an instantiation of IDependency.
e.g.: this is legal:
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) {
...
}
}
Obviously, this is less than ideal. You can clean this up slightly, by pulling it into a template method (e.g. try_to_attach_dependency<T>
, and then repeatedly calling it with different types until it succeeds.
Other problem: You're trying to attach a bunch of IDependency objects to your IDependent object -- this requires all IDependency objects to be of the same template specialization (i.e. you cannot mix <int> with <wchar_t>). You can check for this at run time (since we're doing dynamic casting) and either ignore different ones, or throw an error if the IDependency objects are not homogeneous. But you cannot enforce this at compile time. That's the danger of dynamic_cast.
So, what can you do about it? I guess this is the part you really care about.
You have a few options:
You could create an actual base class of IDependency. This would allow you to pass IDependency* objects around, which seems like a good idea at first, but the problem is the Get
method. The whole reason this is a template class, is so that you can have a method which returns different types, depending on which specialization is being used. You can't do that with base types (without some creativity).
Instead of a Obj Get()
method, you could have a void Set(ITest*)
method. That way, instead of ITest asking IDependency for information, you make IDependency tell ITest the information. Doing it this way is an inversion of sorts, but it allows you to create a non-template base class for the IDependency classes. I'm not sure how you planned on using Get, so this reversal mechanism may work for you.
Alternately, you can get rid of the variable argument list. Va_args is often a bad idea (no safety), and by pushing that up to the level of the caller, you can templatize the function and do the following:
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 );
}
You can then use this by doing the following:
std::vector< IDependency<const wchar_t*> > dependencies; // or whatever type you want
dependencies.push_back(&foo);
// ... more dependencies as needed
Register(&bar, dependencies);
This puts slightly more effort on the caller (i.e. you can't just dump it all into a comma-separated list), but it works, and it's safer. Note that we are now able to use static_cast
which is checked at compile time.
This approach does limit you to only having dependencies based on the same type specialization. If you want to have type-independent dependencies, you will need a non-template way to hold dependencies.
Holy cow, that was a long post. Anyway, I hope it helps. Let me know if it works, or if you have any questions. If I think of anything else, I'll add it here.
精彩评论