【问题标题】:C++: How to store various member functions of different classes for later useC++:如何存储不同类的各种成员函数以备后用
【发布时间】:2017-09-18 17:44:28
【问题描述】:

提前感谢您的支持。

我正在使用 C++11,我想存储一些类的公共成员函数以供以后用作回调函数;例如我想存储一些与此模板匹配的函数:void(classname::*)(void)。据我所知,我也必须存储他们的对象,这很好。例如:

// PSEUDO CODE
class A {
    public:
    void myfunc() {}
}myobj;

class B {
    public:
    void myfunc2() {}
}myobj2;

/* storing */
mystorageclass storage;
storage.push(&myobj, &A::myfunc);
storage.push(&myobj2, &B::myfunc2);

/* call them back */
(storage[0].object->*(storage[0].callback))();
(storage[1].object->*(storage[1].callback))();

有没有安全通用的方法来做到这一点?实际上我已经找到了一种方法,但我不确定它在处理器或编译器之间的可移植性。

//test.cpp - compiled with: g++ test.cpp -o test -std=c++11
#include <iostream>
#include <vector>

class A {
    public:
    void myfunc() { std::cout << "Test A::myfunc()" << std::endl; }
}myobj;

class B {
    public:
    void myfunc2() { std::cout << "Test B::myfunc2()" << std::endl; }
}myobj2;

struct Callback {
    void* object;
    void(* method)(void*);
};

std::vector<Callback> callbackList;

template<typename FunctionPtr>
void add(void* object, FunctionPtr fptr) {
    Callback cb;
    cb.object = object;
    cb.method = (void(*)(void*))(*(void**)(&fptr));
    callbackList.push_back(cb);
}

int main() {
    //add to list for later use
    add(&myobj, &A::myfunc);
    add(&myobj2, &B::myfunc2);

    //call them back
    callbackList[0].method(callbackList[0].object);
    callbackList[1].method(callbackList[1].object);
}

还有另一种方法;我觉得这样更安全:

//test2.cpp - compiled with: g++ test2.cpp -o test2 -std=c++11
#include <iostream>
#include <vector>

class A {
    public:
    void myfunc() { std::cout << "Test A::myfunc()" << std::endl; }
}myobj;

class B {
    public:
    void myfunc2() { std::cout << "Test B::myfunc2()" << std::endl; }
}myobj2;

struct Callback {
    struct A;
    A* object;
    void(A::* method)();
    void call() {
        (object->*method)();
    }
};

std::vector<Callback> callbackList;

template<typename FunctionPtr>
void add(void* object, FunctionPtr fptr) {
    Callback cb;
    cb.object = (Callback::A*)object;
    cb.method = (void(Callback::A::*)())(fptr);
    callbackList.push_back(cb);
}

int main() {
    //add to list for later use
    add(&myobj, &A::myfunc);
    add(&myobj2, &B::myfunc2);

    //call them back
    callbackList[0].call();
    callbackList[1].call();
}

这些用法安全吗?或者你有什么建议来代替这些。 谢谢。

【问题讨论】:

  • 使用std::function
  • 你试过编译第二个版本吗?它不应该编译。您不能使用 void* 调用成员函数。
  • 我试过了,但这些是我得到的最好的结果。 @CaptainObvlious
  • 按照我给你的链接中的例子。首先使用std::function 来处理一些小而琐碎的事情,以便习惯它。一旦你熟悉了它,试着将它应用到你的代码中。
  • 您想将函数存储在文件中还是让操作系统稍后加载它们?

标签: c++ c++11


【解决方案1】:

第二个版本的问题

排队

cb.method = (void(*)(void*))(*(void**)(&fptr));

您正在将函数指针转换为void**。我不确定标准是否支持。我的猜测是不是。我知道标准不支持将函数指针转换为void*。详情请见Print an address of function in C++, g++/clang++ vs vc++ , who is rght?

然后,你继续使用:

callbackList[1].method(callbackList[1].object);

这依赖于编译器在调用类的成员函数时将this 作为第一个隐藏参数传递的约定。不能保证所有编译器都使用该方法。标准没有明确说明。

第三个/最后一个版本的问题

您正在使用:

cb.object = (Callback::A*)object;
cb.method = (void(Callback::A::*)())(fptr);

无论对象类型是A 还是B。这是导致未定义行为的原因。该标准不支持将对象指针转换为任何旧指针类型。

更简洁的版本

使用Callback 的基类。

struct Callback {
   virtual ~Callback() = 0;
   virtual void call() = 0;
};

然后,为真正的Callbacks 使用类模板。

template <typename T>
struct RealCallback : public Callback
{
   RealCallback(T* obj, void (T::*m)(void)) : object(obj), method(m) {}

   virtual void call()
   {
      (object->*method)();
   }

   T* object;
   void (T::*method)();
};

这样,您将无法存储Callback 对象的列表,但可以存储shared_ptr&lt;Callback&gt;s 的列表。

std::vector<std::shared_ptr<Callback>> callbackList;

这是一个完整的程序,不依赖任何丑陋的演员表并且完美运行。

//test.cpp - compiled with: g++ test.cpp -o test -std=c++11
#include <iostream>
#include <vector>
#include <memory>

class A {
    public:
    void myfunc() { std::cout << "Test A::myfunc() on " << this << std::endl; }
}myobj;

class B {
    public:
    void myfunc2() { std::cout << "Test B::myfunc2() on " << this << std::endl; }
}myobj2;

struct Callback {
   virtual void call() = 0;
};

template <typename T>
struct RealCallback : public Callback
{
   RealCallback(T* obj, void (T::*m)(void)) : object(obj), method(m) {}

   virtual void call()
   {
      (object->*method)();
   }

   T* object;
   void (T::*method)();
};

std::vector<std::shared_ptr<Callback>> callbackList;

template<typename T>
void add(T* object, void (T::*fptr)()) {
    RealCallback<T>* cb = new RealCallback<T>(object, fptr);
    callbackList.push_back(std::shared_ptr<Callback>(cb));
}

int main() {
    //add to list for later use
    add(&myobj, &A::myfunc);
    add(&myobj2, &B::myfunc2);

    std::cout << "myobj: " << &myobj << std::endl;
    std::cout << "myobj2: " << &myobj2 << std::endl;

    //call them back
    callbackList[0]->call();
    callbackList[1]->call();
}

更新,以回应 Yakk 的评论

我认为 Yakk 的建议很有道理。您可以使用

删除 CallbackRealCallback
using Callback = std::function<void()>;

std::vector<Callback> callbackList;

那么,add 可以简化为:

template<class T>
void add(T* object, void(T::*ptr)()) {
   callbackList.push_back([object, ptr]{ (object->*ptr)();});
}

随着这些变化,main 需要稍微更新为:

int main() {
    //add to list for later use
    add(&myobj, &A::myfunc);
    add(&myobj2, &B::myfunc2);

    std::cout << "myobj: " << &myobj << std::endl;
    std::cout << "myobj2: " << &myobj2 << std::endl;

    // Updated. Can't use callbackList[0]->call();

    //call them back
    callbackList[0]();
    callbackList[1]();
}

【讨论】:

  • 感谢您快速而精彩的回答,我很高兴看到我的解决方案得到了很好的改进。我在想如何跳转和存储那些“类型变量”指针。我考虑过 C++14 提供的模板变量,但出于可移植性的原因,我真的不想使用 C++14。谢谢,“抽象基类”的存储方法真是太聪明了。
  • @ÖmerGöktaş,不客气。请注意,我在基类中添加了 virtual 析构函数。
  • 我不明白你为什么要实施一个弱版本的std::function 而不是......只使用std::function
  • @Yakk,您的建议肯定会简化用户代码。
  • 而且它不涉及堆分配。 ;)
【解决方案2】:

尝试std::functionstd::bind两者都需要保留对实例的引用:

#include <string>
#include <iostream>
#include <functional>

using namespace std;
class MyClass
{
    int _value;
public:
    MyClass(int value)
    {
        _value = value;
    }

    void food()
    {
        cout << "Foo is doing something whit value: " << _value << endl;
    }
    void bar()
    {
        cout << "Bar is doing something whit value: " << _value << endl;
    }
};


int main()
{

    MyClass* c1 = new MyClass(1);
    MyClass* c2 = new MyClass(2);

    cout << "Using 'std::function':" << endl;

    std::function<void(MyClass&)> food = &MyClass::food;
    std::function<void(MyClass&)> bar = &MyClass::bar;

    food(*c1);
    bar(*c1);
    food(*c2);
    bar(*c2);


    cout << "Using 'std::bind':" << endl;

    auto foodBind = std::bind(&MyClass::food, std::placeholders::_1);
    auto barBind = std::bind(&MyClass::bar, std::placeholders::_1);

    foodBind(*c1);
    barBind(*c1);
    foodBind(*c2);
    barBind(*c2);

    system("PAUSE");
};

输出是:

【讨论】:

    【解决方案3】:

    Callback 替换为std::function&lt;void()&gt;

    add 替换为

    template<class T, class R, class U>
    void add(T* object, R(U::*ptr)()) {
      Callback cb = [object, ptr]{ object->ptr(); };
      callbackList.push_back(cb);
      // or just
      // callbackList.push_back([object, ptr]{ object->ptr(); });
    }
    

    请注意,这支持传入指向父成员函数的指针,以及不返回 void 并丢弃结果的回调。

    std::function 存储一个通用的“稍后调用”。您在std::function&lt;signature&gt; 的模板签名参数中传递了与返回值兼容的类型,以及与稍后要调用的内容兼容的参数。在这种情况下,&lt;void()&gt;

    【讨论】:

    • callbackList.push_back(std::bind(ptr, object));?
    • @MooingDuck 你为什么要使用std::bind?我的意思是,曾经?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-14
    • 1970-01-01
    • 2021-10-27
    相关资源
    最近更新 更多