【问题标题】:Pointer-to-Function and Pointer-to-Object Semantics指向函数和指向对象语义的指针
【发布时间】:2016-08-18 17:22:16
【问题描述】:

我在获取部分限定的函数对象以稍后在另一个线程中使用可变参数调用时遇到问题。

在 GCC 中,我一直在使用我制作的宏和 typedef,但我正在完成我的项目并尝试清除警告。

#define Function_Cast(func_ref) (SubscriptionFunction*) func_ref
typedef void(SubscriptionFunction(void*, std::shared_ptr<void>));

使用如下所示的 Function_Cast 宏会导致“警告:有条件地支持函数指针和对象指针之间的转换”

Subscriber* init_subscriber = new Subscriber(this, Function_Cast(&BaseLoaderStaticInit::init), false);

我真正需要的只是一个可以创建 std::bind 对象的指针。这通常是怎么做的?

另外,这个有条件支持的东西真的很烦人。我知道在 x86 上我的代码可以正常工作,并且我知道依赖 sizeof(void*) == sizeof(this*) 来处理所有这些*的局限性。

另外,有没有办法让 clang 将函数指针视为数据指针,以便我的代码能够编译?我很想知道它失败的程度(如果失败的话)。

相关代码:

#define Function_Cast(func_ref) (SubscriptionFunction*) func_ref
typedef void(SubscriptionFunction(void*, std::shared_ptr<void>));
typedef void(CallTypeFunction(std::shared_ptr<void>));

Subscriber(void* owner, SubscriptionFunction* func, bool serialized = true) {
    this->_owner = owner;
    this->_serialized = serialized;
    this->method = func;

    call = std::bind(&Subscriber::_std_call, this, std::placeholders::_1);
}

void _std_call(std::shared_ptr<void> arg) { method(_owner, arg); }

【问题讨论】:

  • 非静态成员函数不能用作指向(非成员)函数的普通指针。你有 C++11,为什么不使用std::functionstd::bind
  • @JoachimPileborg 我首先尝试使用 std::function,主要是因为名称,但我在将 std::function&lt;void(Type1::this*, shared_ptr)&gt; 转换为 std::function&lt;void(void*, shared_ptr)&gt; 时遇到了问题,以便我可以存储很多它们在相同的结构中。回答此转换也将是对问题的回答。
  • @lorro 我没有尝试过 lambda,因为它们似乎必须使用捕获子句中的已知参数构造,而我需要传递未知的 shared_ptr。另外,我可以在同一个类向量结构中存储不同的 lambda 表达式吗?
  • 只是一个观察,但需要指向void 的指针通常是设计问题的标志。类型安全是 C++ 的优点之一。积极尝试绕过它最终意味着代码的新读者更难维护和掌握更糟糕的代码(对于“新读者”,当你开始忘记几个月时,我包括你自己详情)。
  • @JoachimPileborg 感谢您的关注,但(据我所知)这是少数必须以 void* 形式进行存储的情况之一。我在上面编写了一个 typedef,它允许编译器强制执行函数声明。如果您可以将不同类的函数对象存储在同一个容器中,那么我很乐意改用它。另外,我不是“新读者”。这个问题源于一个类似于英特尔 TBB 的项目的 sn-p,但几乎是全自动的,并且是未来提交给 SC 会议的主要代码。

标签: c++ pointers c++14 clang++ c++17


【解决方案1】:

这里的问题是您尝试使用 member-function 指针代替函数指针,因为您知道,在底层,它通常实现为 @987654324 @。

struct S {
    void f() {}
};

using fn_ptr = void(*)(S*);

void call(S* s, fn_ptr fn)
{
    fn(s);
    delete s;
}

int main() {
    call(new S, (fn_ptr)&S::f);
}

http://ideone.com/fork/LJiohQ

但不能保证这会真正起作用,而明显的情况(虚拟函数)可能不会。

成员函数的传递方式如下:

void call(S* s, void (S::*fn)())

并像这样调用:

(s->*fn)();

http://ideone.com/bJU5lx

当人们想要支持不同的类型时,他们如何解决这个问题是使用蹦床,它是一个非成员函数。您可以使用静态 [member] 函数或 lambda:

auto sub = new Subscriber(this, [](auto* s){ s->init(); });

或者,如果您希望调用站点的类型安全,可以使用模板化构造函数:

template<typename T>
Subscriber(T* t, void(T::*fn)(), bool x);

http://ideone.com/lECOp6

如果您的 Subscriber 构造函数采用 std::function&lt;void(void))&gt; 而不是函数指针,您可以传递捕获 lambda 并消除采用 void* 的需要:

new Subscriber([this](){ init(); }, false);

【讨论】:

  • 我最喜欢这个解决方案,因为它解释了我认为它应该起作用的原因,为什么它偶尔会起作用,并根据我想要重构的样式提供了几种替代方案。我最终编写了相当多的内联汇编,因为使用 std::function 的开销远大于实际的函数调用。
【解决方案2】:

通常是这样的:

#include <functional>
#include <memory>

struct subscription
{
  // RAII unsubscribe stuff in destructor here....
};

struct subscribable
{
  subscription subscribe(std::function<void()> closure, std::weak_ptr<void> sentinel)
  {
    // perform the subscription

    return subscription {
      // some id so you can unsubscribe;
    };
  }


  //
  //

  void notify_subscriber(std::function<void()> const& closure, 
                         std::weak_ptr<void> const & sentinel)
  {
    if (auto locked = sentinel.lock())
    {
      closure();
    }
  }
};

【讨论】:

    猜你喜欢
    • 2021-07-25
    • 2012-10-12
    • 2011-04-26
    • 2023-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多