问题
I am trying to figure out a way to have a container of functors so that I can pass a value into the functors and have it be modified however I am having trouble allowing the functors to not be restricted in what types they can be passed and the amount of arguments they can take.
My actual use for this is that I have a series of functors which all change a 3D vector in some way based on the input. By being able to store these functors in a container I can manipulate the order in which they are called in and end up with a different resulting vector by iterating over the container passing each functor the vector. I'm using these vectors to determine the position, colour, acceleration, etc. of a particle. So ultimately I can create vastly different particle effects by taking this modular approach and eventually I can have the functor order be defined by a file during runtime. That's my final goal :)
The only way I have made this work is with inheritance and a bunch of void pointers which makes the code extremely hard to follow, debug and use.
Here's my code so far that will hopefully demonstrate what I am trying to do better than what I have typed above. Please be aware I am highly out of my comfort zone so this code may be horrible and make some of you gurus want to beat me with a stick.
#include <iostream>
#include <vector>
//the base functor class
struct Functor {
//an integer so we can tell how many arguments the functor takes
unsigned num_arguments;
Functor() : num_arguments(0) {}
//I'm making the arguments default to 0 so the compiler doesn't complain about not giving enough arguments
virtual void operator()(void* R, void* A1 = 0, void* A2 = 0, void* A3 = 0) = 0;
};
template<typename R, typename A1>
struct Double : public Functor {
Double() { num_arguments = 1; }
void operator()(void* i, void* a, void*, void*) {
//having to cast everything out of void pointers so it can be used
A1& first = *static_cast<A1*>(a);
*static_cast<R*>(i) = first * 2;
}
};
template<typename R, typename A1, typename A2>
struct Sub : public Functor {
Sub() { num_arguments = 2; }
void operator()(void* i, void* a, void* b, void*) {
//having to cast everything out of void pointers so it can be used
A1& first = *static_cast<A1*>(a);
A2& second = *static_cast<A2*>(b);
*static_cast<R*>(i) = first - second;
}
};
int main() {
//our container for the functors
std::vector<Functor*> functors;
functors.push_back(new Double<int, int>);
functors.push_back(new Sub<int, int, int>);
for(int i = 0; i < functors.size(); ++i) {
int result;
int first = 1, second = 2;
Functor& f = *functors[i];
if(f.num_arguments == 1) {
f(&result, &first);
} else if(f.num_arguments == 2){
f(&result, &first, &second);
}
std::cout << result << std::endl;
}
Functor* float_sub = new Sub<float, float, float>;
float result;
float first = 0.5f, second = 2.0f;
(*float_sub)(&result, &first, &second);
std::cout << result << std::endl;
functors.push_back(float_sub);
//The functors vector now contains 3 different types of functors:
//One that doubles an integer
//One that subtracts two integers
//One that subtracts two floats
std::cin.get();
return 0;
}
Side note: I am expecting some people to tell me to use so-and-so from the boost libraries. Whilst I appreciate knowing that option is there I would still like to know a better way to implement this without any outside libraries as this is something of a learning exercise for myself.
Edit
Okay so having learnt of stdarg
and boost::any
I think I can see a way to make this work nicely and am trying it out :)
Solution 2
Okay I have reworked the code using the boost::any
and cstdarg
for what I think is a better solution. This doesn't use void pointers and doesn't restrict the amount of arguments the functors can have. The newer code also allows you to pass in by value, using void pointers everything had to be by reference which would cause problems trying to do: Sub(&result, 1, 1)
#include <iostream>
#include <vector>
#include <cstdarg>
#include <boost\any.hpp>
struct Functor {
unsigned num_arguments;
Functor() : num_arguments(0) {}
virtual void operator()(boost::any, ...) = 0;
};
template<typename R, typename A1>
struct Double : public Functor {
Double() { num_arguments = 1; }
void operator()(boost::any r, ...) {
R* out = boost::any_cast<R*>(r);
va_list args;
va_start(args, r);
A1 first = va_arg(args, A1);
va_end(args);
*out = first * 2;
}
};
template<typename R, typename A1, typename A2>
struct Sub : public Functor {
Sub() { num_arguments = 2; }
void operator()(boost::any r, ...) {
R* out = boost::any_cast<R*>(r);
va_list args;
va_start(args, r);
A1 first = va_arg(args, A1);
A2 second = va_arg(args, A2);
va_end(args);
*out = first - second;
}
};
int main() {
std::vector<Functor*> functors;
functors.push_back(new Double<int, int>);
functors.push_back(new Sub<int, int, int>);
functors.push_back(new Sub<int, float, float>);
int result = 0;
for(int i = 0; i < functors.size(); ++i) {
(*functors[i])(&result, 2, 2);
std::cout << result << std::endl;
}
std::cin.get();
return 0;
}
and now I finally have the privilege to upvote :D
回答1:
Have you thought about using variadic argument lists from the stdarg library? I am not sure it is a better solution, but it is different. For example:
#include <vector>
#include <cstdarg>
#include <iostream>
using namespace std;
template< typename type >
void sub( void* out, ... ){
// Initialize the variadic argument list.
va_list args;
va_start( args, out );
// Extract our arguments.
type lhs = va_arg( args, type );
type rhs = va_arg( args, type );
// Close the variadic argument list.
va_end( args );
// Function logic goes here.
*(static_cast<type*>(out)) = lhs - rhs;
}
int main( void ){
typedef void (*functor)( void* out, ... ); // Function type.
typedef vector< functor > FunctorList; // Function list type.
FunctorList fList;
fList.push_back( &sub<int> );
int diff;
fList[0]( &diff, 3, 5 );
cout << diff << endl;
return 0;
}
回答2:
I did not realize you specifically wanted function objects (should have clicked from your usage of "functor" and not "function pointer"). Anyways, here is my previous answer using function objects instead. It works just the same:
#include <vector>
#include <cstdarg>
#include <iostream>
using namespace std;
struct FunctorBase {
virtual void operator()( void* out, ... ) = 0;
}; // end FunctorBase
template< typename T >
struct Subtract : public FunctorBase {
void operator()( void* out, ... ){
// Initialize the variadic argument list.
va_list args;
va_start( args, out );
// Extract our arguments
T lhs = va_arg( args, T );
T rhs = va_arg( args, T );
// Close the variadic argument list.
va_end( args );
// Function logic goes here.
*(static_cast<T*>(out)) = lhs - rhs;
}
};
int main( void ){
typedef vector< FunctorBase* > FunctorList; // Function list type.
FunctorList fList;
fList.push_back( new Subtract<int>() );
int diff;
(*fList[0])( &diff, 3, 5 );
cout << diff << endl;
return 0;
}
来源:https://stackoverflow.com/questions/8581902/container-of-different-functors