Is that possible to make a callback interface with only function-member attribute?

為{幸葍}努か 提交于 2019-12-07 19:49:23

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!