C ++调用一个不带参数参数的回调 - 或者更好的解决方案

时间:2017-10-26 18:58:34

标签: c++ function pointers callback

我正在为嵌入式项目构建一个c ++类库。 我有一个概念是一个名为Task的类。 任务有2名成员: 整数id和回调函数指针

回调由用户创建并提供给类的构造函数。

我想向用户提供以下选项:

  • 用户可以使用void(*)(void)
  • 类型的功能
  • 用户可以使用void (*)(unsigned int)类型的功能。调用回调时,该参数应该是任务的id。
  • 用户可以将上述功能类型的组合用于不同的任务

任务还有一个调用回调函数的execute()方法。如果需要,此方法还应将task id作为参数提供

一种解决方案是在Task中添加一个名为'has_arguments'的布尔成员,并重载构造函数以根据代码中给出的函数指针的类型来设置它。 我也可以使用union作为回调成员,或使用void指针并将其转换为执行期间适合的任何内容。 执行任务将涉及检查'has_arguments'成员,将指针转换为适当的类型并以正确的方式调用回调。

但有没有办法避免这个额外的成员并检查?

我试图将函数指针强制转换为void(*)(unsigned int),并在调用它时始终提供参数。它起作用了。

调用一个不带参数的函数,带参数是不是很糟糕? 它对我有用,但我认为这是一个非常糟糕的做法。 关于我可以实现什么的任何其他想法?

我不想指示用户使用可变参数列表创建函数。我想让他们自由地使用简单的任务不可知功能, 或者制作能够识别其任务ID的更智能的功能

4 个答案:

答案 0 :(得分:2)

  

那么调用一个不带参数参数的函数是不是很糟糕?

当然可以!

  

它适用于我,但我认为这是一个非常糟糕的做法

"它对我有用"如果您的程序有未定义的行为,那么它不是一个非常有效的参数。因为它可能不适合我。

  

我可以改变其他任何想法吗?

在我看来,标记的联合实际上是实现此类事情的最佳方式。如果可以的话,您可以随时使用boot::variantstd::variant来获得更安全的标记联盟。

真的,只有一个额外的成员(bool)并不会破坏你的表现,对于你必须引入的其他分支也是如此,即使在嵌入式分支上也是如此。首先测量,如果发现分支确实是瓶颈,则可以随时进行优化。 :)

答案 1 :(得分:1)

如果由于嵌入式平台使用std::function和标记的限制而无法使用union,请通过重载的setter或ctor正确设置type并在通话中使用{{1} } switch

type

答案 2 :(得分:1)

通过已转换为其他类型的函数指针调用函数会调用未定义的行为(参见thins online C++ standard draft有关函数指针的转换):

  

函数指针可以显式转换为函数指针   不同类型的。 通过a调用函数的效果   指向与函数类型不同的函数类型([dcl.fct])的指针   函数定义中使用的类型是未定义的。除此之外   将“指向T1的指针”的prvalue转换为“指向的指针”   T2“(其中T1和T2是函数类型)并返回其原始类型   产生原始指针值,这种指针的结果   转换未指定。 [注意:另请参阅[conv.ptr]了解更多信息   指针转换的详细信息。 - 结束说明]

因此,您不应该使用它/按照您的问题所述进行操作。

克服这个问题的一种方法是强制为一个和另一个案例存储正确的函数指针。这可以通过鉴别器/类型数据成员和两个单独的函数指针成员(或这两个单独的函数指针的并集)来完成,如@Slava的答案所述。

另一种方法可能是区分不同的任务类型,并使用继承和覆盖来正确执行回调。当然,这会影响您创建/使用任务的方式。但也许它比类型成员和联合以更可扩展的方式解决您的问题。

typedef void(*VoidCallbackType)(void) ;
typedef void(*IdCallbackType)(unsigned) ;

struct Task {

    virtual void doCallback() = 0;
};

struct TaskWithId : Task {

    unsigned id;
    IdCallbackType cb;

    TaskWithId(unsigned id, IdCallbackType cb) : id(id), cb(cb) { }
    virtual void doCallback() {
        cb(id);
    }
};

struct TaskWithoutId: Task {

    VoidCallbackType cb;

    TaskWithoutId(VoidCallbackType cb) : cb(cb) { }
    virtual void doCallback() {
        cb();
    }
};

答案 3 :(得分:0)

您可以在封面下创建看起来像相同界面的内容。也就是说,自动创建帮助程序包装函数,如果回调函数不需要它,可以丢弃任务ID。

class Task {

    template <void (*CB)(void)> struct task_void {
        static void cb (int) { CB(); }
        operator Task () const { return Task(*this); }
    };

    template <void (*CB)(int)> struct task_int {
        static void cb (int id) { CB(id); }
        operator Task () const { return Task(*this); }
    };

    static volatile int next_id_;

    int id_;
    void (*cb_)(int);

    template <typename CB> Task (CB) : id_(next_id_++), cb_(CB::cb) {}

public:
    template <void (*CB)(void)> static Task make () { return task_void<CB>(); }
    template <void (*CB)(int)> static Task make () { return task_int<CB>(); }
    void execute () { cb_(id_); }
};

辅助模板可以访问私有构造函数以进行初始化。辅助模板可以选择是否隐藏参数。

现在,您可以使用任何一种回调创建任务。

void cb_void () {
    std::cout << __func__ << std::endl;
}

void cb_int (int id) {
    std::cout << __func__ << ':' << id << std::endl;
}

//...
    Task t1 = Task::make<cb_void>();
    Task t2 = Task::make<cb_int>();
    t1.execute();
    t2.execute();