问题
Context:
Embedded c++ with no heap use.
I want to master my code (its size included), so I would prefer not to use standard lib such as std::function.
1st Approach:
Let's take this example (which is a simplified version of my code) using a modified version of the CRTP:
Note: the method of my callback could have theses 2 signatures: bool (ChildCrtp::*)(void);
and void (ChildCrtp::*)(int)
(one for action, one for condition).
#include <iostream>
#include <stdint.h>
using namespace std;
void* operator new(size_t size)
{
cout << "ERROR HEAP USED" << endl;
}
template <typename FunctionType = void, typename... ArgumentType>
class GenericCallback
{
public:
virtual ~GenericCallback(){}
virtual FunctionType Execute(ArgumentType... arg) = 0; //!< execute callback
virtual bool IsValid() const = 0; //!< check if callback is valid
};
template <typename ObjectType, typename FunctionType = void, typename... ArgumentType>
class Callback : public GenericCallback<FunctionType, ArgumentType...>
{
public:
Callback() ://!< Default constructor
pObject_m(0),
pFunction_m(0)
{
}
Callback(ObjectType* pObject_m, FunctionType(ObjectType::*pFunction_m)(ArgumentType...))//!< Constructor
{
this->pObject_m = pObject_m;
this->pFunction_m = pFunction_m;
}
virtual FunctionType Execute(ArgumentType... arg)//!< execute callback implementation
{
return (pObject_m->*pFunction_m)(arg...);
}
virtual bool IsValid(void) const//!< callback validity check implementation
{
return (pObject_m != 0) && (pFunction_m != 0);
}
private:
ObjectType* pObject_m; //!< pointer to object where the callback is defined
FunctionType(ObjectType::* pFunction_m)(ArgumentType...); //!< pointer to the callback (function-member) of the object
};
template<typename ChildCrtp>
class Interface
{
public:
using FooSpecificCallback = Callback<ChildCrtp, bool>;
virtual int getValue(void) = 0;
bool IsPositive() { return (getValue() > 0); };
bool IsNegative(void) { return (getValue() < 0); };
bool IsEven(void) { return ((getValue() % 2) == 0); };
bool IsOdd(void) { return ((getValue() % 2) == 1); };
FooSpecificCallback isPositive_ = FooSpecificCallback(static_cast<ChildCrtp*>(this), &Interface::IsPositive);//line to be removed
FooSpecificCallback isNegative_ = FooSpecificCallback(static_cast<ChildCrtp*>(this), &Interface::IsNegative);//line to be removed
FooSpecificCallback isEven_ = FooSpecificCallback(static_cast<ChildCrtp*>(this), &Interface::IsEven);//line to be removed
FooSpecificCallback isOdd_ = FooSpecificCallback(static_cast<ChildCrtp*>(this), &Interface::IsOdd);//line to be removed
};
class Mother
{
public:
using FooGenericCallback = GenericCallback<bool>* ;
int getValue(){return x_;};
void storeCallback(FooGenericCallback pCallback){pCallback_ = pCallback;};
bool callCallback(){return (pCallback_->IsValid() == false)?:pCallback_->Execute();};
private:
int x_ = 3;
FooGenericCallback pCallback_;
};
class Child : public Mother, public Interface<Child>
{
public:
int getValue(){return Mother::getValue();}
void setup(void){storeCallback(&isPositive_);}
};
int main()
{
Child c;
c.setup();
cout << std::boolalpha << "Is " << c.getValue() << " positive? " << c.callCallback() << endl;
return 0;
}
This design has several problems:
- the callback objects are stored twice
- the interface has non function-member attributes: the callbacks.
- it is painful to write a lib because you need to write the method and the callback, and you have to define it in all the classes that uses your callbacks!
- maybe the use of CRTP is not suited. Why am I using CRTP? See [here].(How to define a template specific type that can be inherited?)
Solution?
Is that even possible?
Am I on the right track? If not, what is the right tool?
I've googled around and found several tracks but still cannot figure out how to do it:
1) using template typedef
Do not see how
2) function as template argument
I know that passing a function as a template argument is possible/valid
But my attempt was not successful:
#include <iostream>
#include <stdint.h>
using namespace std;
void* operator new(size_t size)
{
cout << "ERROR HEAP USED" << endl;
}
template <typename FunctionType = void, typename... ArgumentType>
class GenericCallback
{
public:
virtual ~GenericCallback(){}
virtual FunctionType Execute(ArgumentType... arg) = 0; //!< execute callback
virtual bool IsValid() const = 0; //!< check if callback is valid
};
template <typename ObjectType, typename FunctionType = void, typename... ArgumentType>
class Callback : public GenericCallback<FunctionType, ArgumentType...>
{
public:
Callback() ://!< Default constructor
pObject_m(0),
pFunction_m(0)
{
}
Callback(ObjectType* pObject_m, FunctionType(ObjectType::*pFunction_m)(ArgumentType...))//!< Constructor
{
this->pObject_m = pObject_m;
this->pFunction_m = pFunction_m;
}
virtual FunctionType Execute(ArgumentType... arg)//!< execute callback implementation
{
return (pObject_m->*pFunction_m)(arg...);
}
virtual bool IsValid(void) const//!< callback validity check implementation
{
return (pObject_m != 0) && (pFunction_m != 0);
}
private:
ObjectType* pObject_m; //!< pointer to object where the callback is defined
FunctionType(ObjectType::* pFunction_m)(ArgumentType...); //!< pointer to the callback (function-member) of the object
};
template<typename ChildCrtp>
class Interface
{
public:
using FooSpecificCallback = Callback<ChildCrtp, bool>;
using FooPrototype = bool(Interface::*)();
template<FooPrototype op>
FooSpecificCallback* checkIf(void)
{
//I'm trying to take the address of this temporary object, which is not legal in C++.
return &FooSpecificCallback(static_cast<ChildCrtp*>(this), op);
}
virtual int getValue(void) = 0;
bool IsNegative() { return (getValue() < 0); };
};
class Mother
{
public:
using FooGenericCallback = GenericCallback<bool>*;
int getValue(){return x_;};
void storeCallback(FooGenericCallback pCallback){pCallback_ = pCallback;};
bool callCallback(){return (pCallback_->IsValid() == false)?:pCallback_->Execute();};
private:
int x_ = 3;
FooGenericCallback pCallback_;
};
class Child : public Mother, public Interface<Child>
{
public:
int getValue(){return Mother::getValue();}
void setup(void){storeCallback(checkIf<&Child::IsNegative>());}
};
int main()
{
Child c;
c.setup();
cout << std::boolalpha << "expectFalse: " << c.callCallback() << endl;
return 0;
}
I get the following error
error: taking address of temporary [-fpermissive]
As it is not possible take the address of a temporary object, which is not legal in C++.
The problem with this callback interface is that it needs a pointer to store the object "FooGenericCallback", which cannot be a "FooSpecificCallback" because the object type is not known in the mother class.
3) other way to implement callback as an interface
how to implement callback as an interface
But the solution still uses object to store the function-members in the interface (or in the interface's children).
4) Lambdas...
I know that lambdas would have simplified my life, indeed I did it first with lambdas and the code size was doubled from 60kB to 120kB (!), because of the way the lambdas were stored: in std::function. Shall the answer not be "lambda" :)
回答1:
I might have oversimplified your needs, but what's wrong with:
template<typename Base>
class Interface : public Base
{
public:
static bool IsNegative(Base* userData)
{
auto that = static_cast<Base*>(userData);
return that->getValue() < 0;
}
};
class Mother
{
public:
using Callback = bool (*) (Mother*);
int getValue() { return x_; }
void storeCallback(Callback pCallback) { pCallback_ = pCallback; }
bool callCallback() {return pCallback_ ? (*pCallback_)(this) : throw 42;}
private:
int x_ = 3;
Callback pCallback_;
};
class Child : public Interface<Mother>
{
public:
void setup(){ storeCallback(&Interface::IsNegative); }
};
int main()
{
Child c;
c.setup();
std::cout << std::boolalpha << "expectFalse: " << c.callCallback() << std::endl;
}
Demo
回答2:
I'm still not sure if I understand your intention correctly. But following code compile without errors, though I didn't test it any further:
template<typename ChildCrtp>
class MotherInterface
{
protected:
//Callback types
using SomethingBooleanCallback = bool (ChildCrtp::*)();
protected:
//Helper methods
bool AlwaysTrue(void) { return true; };
SomethingBooleanCallback callback;
public:
void UseCallback(SomethingBooleanCallback a) {callback = a;}
bool CallCallback() {return ((ChildCrtp *)this->*callback)();}
};
template<typename ChildCrtp>
class SpecializedInterfaceA : public MotherInterface<ChildCrtp>
{
public:
/// methods to be overridden in child methods where the callbacks need to be bound
virtual int GetValue (void) const = 0;
protected:
///another helper methods
bool IsPositive(void) { return (GetValue() > 0); };
bool IsNegative(void) { return (GetValue() < 0); };
bool IsEven(void) { return ((GetValue() % 2) == 0); };
bool IsOdd(void) { return ((GetValue() % 2) == 1); };
};
template<typename ChildCrtp>
class ChildA1 : public SpecializedInterfaceA<ChildCrtp>
{
public:
//implements the interface
virtual int GetValue (void) const final override { return value;} ;
//bind the interfaces' callback by a reference to the object "isPositive", which contains a pointer to the desired method and a pointer to the object that owns the method)
void BindPositive(void) { this->UseCallback(&ChildA1::IsPositive); };
private:
//an attribute
int value;
};
回答3:
Here are fixed version.
#include <iostream>
#include <stdint.h>
using namespace std;
template <typename FunctionType = void, typename... ArgumentType>
class GenericCallback
{
public:
virtual ~GenericCallback(){}
virtual FunctionType Execute(ArgumentType... arg) = 0; //!< execute callback
virtual bool IsValid() const = 0; //!< check if callback is valid
};
template <typename ObjectType, typename FunctionType = void, typename... ArgumentType>
class Callback : public GenericCallback<FunctionType, ArgumentType...>
{
public:
Callback() ://!< Default constructor
pObject_m(0),
pFunction_m(0)
{
}
Callback(ObjectType* pObject_m, FunctionType(ObjectType::*pFunction_m)(ArgumentType...))//!< Constructor
{
this->pObject_m = pObject_m;
this->pFunction_m = pFunction_m;
}
virtual FunctionType Execute(ArgumentType... arg)//!< execute callback implementation
{
return (pObject_m->*pFunction_m)(arg...);
}
virtual bool IsValid(void) const//!< callback validity check implementation
{
return (pObject_m != 0) && (pFunction_m != 0);
}
private:
ObjectType* pObject_m; //!< pointer to object where the callback is defined
FunctionType(ObjectType::* pFunction_m)(ArgumentType...); //!< pointer to the callback (function-member) of the object
};
template<typename ChildCrtp>
class Interface
{
public:
using FooSpecificCallback = Callback<ChildCrtp, bool>;
using FooPrototype = bool(Interface::*)();
template<FooPrototype op>
FooSpecificCallback* checkIf(void)
{
return new FooSpecificCallback(static_cast<ChildCrtp*>(this), op);
}
virtual int getValue(void) = 0;
bool IsNegative() { return (getValue() < 0); };
};
class Mother
{
public:
using FooGenericCallback = GenericCallback<bool>*;
int getValue(){return x_;};
void storeCallback(FooGenericCallback pCallback){pCallback_ = pCallback;};
bool callCallback(){return (pCallback_->IsValid() == false)?:pCallback_->Execute();};
private:
int x_ = 3;
FooGenericCallback pCallback_;
};
class Child : public Mother, public Interface<Child>
{
public:
int getValue(){return Mother::getValue();}
void setup(void){storeCallback(checkIf<&Child::IsNegative>());}
};
int main()
{
Child c;
c.setup();
cout << std::boolalpha << "expectFalse: " << c.callCallback() << endl;
return 0;
}
PS: This code leaks pointer to callback, so it you need to add code to properly handle it.
回答4:
This solution, inspired from Jarod42's answer, compiles and works.
Change MotherA's attribute x_
to null
, negative
and positive
and check the results.
#include <iostream>
#include <stdint.h>
using namespace std;
static constexpr int STORE_SIZE = 4;
void* operator new(size_t size)
{
cout << "ERROR HEAP USED" << endl;
}
template<typename T, size_t storeSize>
class CallbackStore
{
public:
CallbackStore() : that_(nullptr) {};
CallbackStore(T* that) : that_(that) {};
using CallbackCondition = bool (*) (T*);
using CallbackAction = void (*) (T*,int);
struct Step
{
CallbackCondition pCallbackCondition;
CallbackAction pCallbackAction;
};
void setStep(int stepId,CallbackCondition pCallbackCondition, CallbackAction pCallbackAction)
{
if(stepId<storeSize)
{
store[stepId].pCallbackCondition = pCallbackCondition;
store[stepId].pCallbackAction = pCallbackAction;
}
else
{
cout << "pointer error" << endl;
}
}
void callStep(int stepId, int param)
{
if((stepId<storeSize) &&
(store[stepId].pCallbackCondition != nullptr) &&
(store[stepId].pCallbackAction != nullptr) &&
(that_ != nullptr))
{
bool isActive = (*(store[stepId].pCallbackCondition))(that_);
if(isActive) {(*(store[stepId].pCallbackAction))(that_,param);}
}
else
{
cout << "pointer error" << endl;
}
}
Step store[storeSize];
T* that_;
};
template<typename Base>
class Interface : public Base // interface
{
public:
static bool True(Base* baseInstance)
{
return true;
}
static bool IsNegative(Base* baseInstance)
{
return ((static_cast<Base*>(baseInstance))->getValue() < 0);
}
static bool IsNull(Base* baseInstance)
{
return ((static_cast<Base*>(baseInstance))->getValue() == 0);
}
static void PrintValue(Base* baseInstance, int value)
{
cout << "print this value : " << value << "." << endl;
}
};
class MotherA
{
public:
int getValue() { return x_; }
void setValue(int x) { x_ = x; }
private:
int x_ = -3;
};
class ChildA : public Interface<MotherA>, public CallbackStore<MotherA, STORE_SIZE>
{
public:
ChildA():Interface<MotherA>(), CallbackStore<MotherA, STORE_SIZE>(this){};
void setup()
{
setStep(0, &Interface::IsNegative, &Interface::PrintValue );
setStep(1, &Interface::IsNull, &Interface::PrintValue );
setStep(2, &Interface::IsNull, &Interface::PrintValue );
setStep(3, &Interface::True, &Interface::PrintValue );
}
};
int main()
{
ChildA c;
c.setup();
for(int i = 0; i < STORE_SIZE; i++)
{
c.callStep(i,8);
}
// shall print "print this value : 8." 3 times if x_ is null, twice if x_ is negative.
}
来源:https://stackoverflow.com/questions/56932216/is-that-possible-to-make-a-callback-interface-with-only-function-member-attribut