C++ invalid reference problem
I'm writing some callback implementation in C++.
I have an abstract callback class, let's say:
/** Abstract callback class. */
class callback {
public:
/** Executes the callback. */
void call() { do_call(); };
protected:
/** Callback call implementation specific to derived callback. */
virtual void do_call() = 0;
};
Each callback I create (accepting single-argument functions, double-argument functions...) is created as a mixin using one of the following:
/** Makes the callback a single-argument callback. */
template <typename T>
class singleArgumentCallback {
protected:
/** Callback argument. */
T arg;
public:
/** Constructor. */
singleArgumentCallback(T arg): arg(arg) { }
};
/** Makes the callback a double-argument callback. */
template <typename T, typename V>
class doubleArgumentCallback {
protected:
/** Callback argument 1. */
T arg1;
/** Callback argument 2. */
V arg2;
public:
/** Constructor. */
doubleArgumentCallback(T arg1, V arg2): arg1(arg1), arg2(arg2) { }
};
For example, a single-arg function callback would look like this:
/** Single-arg callbacks. */
template <typename T>
class singleArgFunctionCallback:
public callback,
protected singleArgumentCallback<T> {
/** Callback. */
void (*callbackMethod)(T arg);
public:
/** Constructor. */
singleArgFunctionCallback(void (*callback)(T), T argument):
singleArgumentCallback<T>(argument),
callbackMethod(callback) { }
protected:
void do_call() {
this->callbackMethod(this->arg);
}
};
For user convenience, I'd like to have a method that creates a callback without having the user think about details, so that one can call (this interface is not subject to change, unfortunately):
void test3(float x) { std::cout << x << std::endl; }
void test5(const std::string& s) { std::cout << s << std::e开发者_运维问答ndl; }
make_callback(&test3, 12.0f)->call();
make_callback(&test5, "oh hai!")->call();
My current implementation of make_callback(...)
is as follows:
/** Creates a callback object. */
template <typename T, typename U> callback* make_callback(
void (*callbackMethod)(T), U argument) {
return new singleArgFunctionCallback<T>(callbackMethod, argument);
}
Unfortunately, when I call make_callback(&test5, "oh hai!")->call();
I get an empty string on the standard output. I believe the problem is that the reference gets out of scope after callback initialization.
I tried using pointers and references, but it's impossible to have a pointer/reference to reference, so I failed. The only solution I had was to forbid substituting reference type as T (for example, T cannot be std::string&) but that's a sad solution since I have to create another singleArgCallbackAcceptingReference class accepting a function pointer with following signature:
void (*callbackMethod)(T& arg);
thus, my code gets duplicated 2^n times, where n is the number of arguments of a callback function.
Does anybody know any workaround or has any idea how to fix it? Thanks in advance!
The problem is that in make_callback()
, T
becomes const std::string&
, which in turn becomes T
in your singleArgumentCallback
. U
, however, is a const char*
, so a temporary std::string
object is created and bound to that reference in singleArgumentCallback
. When make_callback()
finishes, that temporary is destroyed, leaving the created singleArgumentCallback
object with a reference to a no longer existing object.
What you will have to do is to first remove references (and, maybe, cv qualifiers) from the types passed into make_callback()
. As Marcelo suggested, Boost.TypeTraits can help you do this, but if you want, it isn't hard to cook up something on your own:
template< typename T > struct remove_ref { typedef T result_type; };
template< typename T > struct remove_ref<T&> { typedef T result_type; };
Then change make_callback()
to:
template <typename T, typename U>
callback* make_callback(void (*callbackMethod)(T), U argument)
{
typedef typename remove_ref<T>::result_type arg_type;
return new singleArgFunctionCallback<arg_type>(callbackMethod, argument);
}
Boost.TypeTraits might help. add_reference converts concrete types to reference types while leaving reference types as-is.
Thanks to sbi, I got this stuff working :-)
The solution I ended up with is here:
template <typename T> struct removeRef { typedef T resultType; };
template <typename T> struct removeRef<T&> { typedef T resultType; };
/** Single-arg callbacks. */
template <typename T, typename U>
class singleArgFunctionCallback:
public callback,
protected singleArgumentCallback<U> {
/** Callback. */
void (*callbackMethod)(T arg);
public:
/** Constructor. */
singleArgFunctionCallback(void (*callback)(T), U argument):
singleArgumentCallback<U>(argument),
callbackMethod(callback) { }
protected:
void do_call() {
this->callbackMethod(this->arg);
}
};
template <typename T, typename U>
callback* make_callback(void (*callbackMethod)(T), U argument) {
typedef T ArgumentType;
typedef typename removeRef<T>::resultType StrippedArgumentType;
return new singleArgFunctionCallback<ArgumentType, StrippedArgumentType>(callbackMethod, argument);
}
If anybody sees any possible improvements, I'd be happy to learn!
Thanks all, Karol
精彩评论