关于 shared_ptr、成员指针运算符 `->*` 和 `std::bind`

11

最近我发现shared_ptr没有指向成员的运算符->*。我创建了一个简单的示例:

template <typename Pointer, typename Function, typename... Args>
auto invoke1(Pointer p, Function f, Args... args) -> decltype((p->*f)(args...))
{
  return (p->*f)(args...);
}
struct A { 
    void g() { std::cout << "A::g()\n"; } 
};
int main() {
  A a;
  invoke1(&a, &A::g); // works!!
  std::shared_ptr<A> sa = std::make_shared<A>();
  invoke1(sa, &A::g); // compile error!!
}

Q1:为什么这样呢?为什么shared_ptr没有这个运算符?

我为shared_ptr添加了这样的运算符,示例开始工作:

template <typename T, typename Result>
auto operator ->* (std::shared_ptr<T> pointer, Result (T::*function)()) ->decltype(std::bind(function, pointer))
{
    return std::bind(function, pointer);
}
template <typename T, typename Result, typename Arg1>
auto operator ->* (std::shared_ptr<T> pointer, Result (T::*function)(Arg1 arg1)) ->decltype(std::bind(function, pointer, std::placeholders::_1))
{
    return std::bind(function, pointer, std::placeholders::_1);
}

Q2:这个运算符的实现是否正确?是否有任何“黄金”规则来实现这样的运算符,可能我重新发明了轮子或者走了完全错误的方向,你认为呢?有没有一种方法可以使用单个函数来实现此运算符,而不是像 std 中占位符数量那么多的函数...

之后,我得出结论,std::bind 可以在我的 invoke 方法中使用。

template <typename Pointer, typename Function, typename... Args>
auto invoke2(Pointer p, Function f, Args... args) 
                     -> decltype(std::bind(f, p, args...)())
{
   return std::bind(f, p, args...)();
}

通过这种方式,我的示例也可以正常工作,无需向 shared_ptr 添加 operator ->*

Q3: 那么,现在是否认为 std::bindoperator->* 的替代品?


你尝试过这样吗:invoke1(sa.get(), &A::g); - Alexis
@Alexis - 是的,我已经尝试过了,当然它可以工作,但我认为这个解决方法对我的问题不重要。 - PiotrNycz
我甚至不知道你可以重载那个运算符。 - user541686
@Mehrdad 曾经有过允许重载空格的讨论,我也看到了逗号运算符的重载。有时候甚至是有用的! - Alice
3个回答

6
简而言之:是的,std::bind可以替代成员函数指针。 为什么?因为成员函数指针很糟糕,它们的唯一目的就是实现委托,这就是为什么std::bind和std::function存在的原因。
关于成员函数指针如何实现,请参见我之前的回答(链接)。简单来说,由于标准不允许在类型转换后调用成员函数指针,所以它们被限制了;这使得它们对于90%的人想要从成员函数指针中获得的行为变得毫无意义:委托。
因此,std::function用于表示抽象的“可调用”类型,而std::bind用于将this绑定到成员函数指针上。你绝对不应该操纵成员函数指针,而应该使用std::bind和std::function。

1
你能否解释一下“不允许在强制转换后进行调用”的含义,可以给出一些例子吗?我读了您链接的答案 - 但是我还是没有看懂它的解释。无论如何,+1x2感谢您的回答。 - PiotrNycz
1
@PiotrNycz 使用成员函数指针作为委托所需的关键功能之一是能够在类之间进行转换,例如从派生类到基类。虽然可以将成员函数指针强制转换,但是调用先前转换过的函数指针是不允许的。这是未定义的行为。这意味着,例如,您无法存储成员函数指针列表并以多态方式调用它们。尽管这在几乎所有编译器中都可以工作,但它不是标准的C++。 - Alice

4

我认为最简单的解决方案是用一对解引用(*)和结构引用(.)运算符替换'结构指针取值'(->)运算符:

template <typename Pointer, typename Function, typename... Args>
auto invoke1(Pointer p, Function f, Args... args) -> decltype(((*p).*f)(args...))
{
  return ((*p).*f)(args...);
}

这就是 INVOKE 的定义方式,它规定了 std::bind 的工作方式。 - K-ballo
1
.* 是一个操作符,不是两个。此外,->*-> 不同。 - Lightness Races in Orbit
你也可以这样做: return p.get()->f(args...); - mchiasson
@mchiasson,它不允许传递原始指针或某些没有“get”函数的自定义指针类型。但我相信每个指针都支持解引用运算符。 - Lol4t0

2

我认为shared_ptr没有->*运算符,因为对于任意数量的参数(这是C++11允许的其他用例),实现它是不可能的。此外,您可以轻松地为调用get()的智能指针添加一个invoke函数的重载,因此不希望使接口复杂化。


对于任意数量的参数来说是不可能的,但是对于比如说最多30个参数是可能的(我的gcc编译器中有29个占位符)。因此从实际角度来看,具有更多参数的函数在现实世界中并不存在。第二件事是我知道有解决方法,@Alexis已经提到了这一点。+1 对于不能用于任何数量的参数的观察。但我仍然想知道,也许还有其他原因,并且仍然希望得到其他子问题的答案。 - PiotrNycz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接