为什么std::shared_ptr没有operator->*运算符?

7

3
std::shared_pointer еҸҜд»ҘжҢҒжңүдёҖдёӘзұ»жҢҮй’ҲгҖӮеӣ жӯӨпјҢеҸҜд»ҘиҮӘ然ең°зј–еҶҷ (sp->*ptr_to_member)() дҪңдёә (sp.get()->*ptr_to_member)() зҡ„з®ҖеҶҷгҖӮе®һзҺ°иҝҷдёӘеҠҹиғҪдјҡзӣёеҪ“жЈҳжүӢгҖӮ - Igor Tandetnik
1
@πάνταῥεῖ 你的评论毫无意义? - Yakk - Adam Nevraumont
3
@Taylor 我猜如果我们不需要点击链接,这个问题会更受欢迎。 - Michael Gazonda
1
这是一个有趣的问题,但我认为SO不能期望以任何确定度回答它。我的猜测是,在标准化过程中没有人想到它或者关心到足够的程度去撰写提案。 - Wintermute
2
@Yakk 这对原始指针也不起作用。 - user743382
显示剩余15条评论
2个回答

4
这可以在C++14之后添加到std::shared_ptr中,而不是您链接的复杂代码:
template<class Method>
auto operator->*(Method&& method){
  return [t=get(),m=std::forward<Method>(method)](auto&&args){
    return (t->*m)(std::forward<decltype(args)>(args)...);
  };
}

添加SFINAE可选项。请注意上面的完美转发并不完美。它也支持某些奇怪的“方法”类型,只要它们生成一个带有operator()但没有其他重要内容的东西。

由于完美转发的不完美,这仍然是不完美的,因此这可能是不更改它并强制使用.get()->*的原因。同时,使用lambda而不是类也会有一些小的不完美之处,但这些可以修复。

克隆接口的解决方案也存在缺陷(它们可以移动两次而不是一次,或者暗示指数级数量的重载)。

有趣的是,我们可以在不修改std的情况下注入上述的->*

namespace notstd{
  template<class...Ts, class Method>
  auto operator->*(std::shared_ptr<Ts...> const& p, Method&& method){
    return [t=p.get(),m=std::forward<Method>(method)](auto&&args){
      return (t->*m)(std::forward<decltype(args)>(args)...);
    };
  }
  template<class...Ts, class Method>
  auto operator->*(std::unique_ptr<Ts...> const& p, Method&& method){
    return [t=p.get(),m=std::forward<Method>(method)](auto&&args){
      return (t->*m)(std::forward<decltype(args)>(args)...);
    };
  }
}

然后,使用 notstd::operator->* 将其考虑在内。有趣的是,与许多相关的成员(如 ->[])不同,->* 不需要成为类的非静态成员才能使用。

我也为 unique_ptr 提供了类似的内容,因为何乐而不为呢。

另一种选择是将 shared_ptr 存储在返回的 lambda 中:这会增加看起来像低级操作的开销,因此我没有这样做,在 unique_ptr 上这样做是不明智的,尽管很有趣。

现在,上述所有内容都很好,但并没有回答问题。

C++03 shared ptr(例如 boost shared ptr)可以添加:

template<class T, class R, class...Args>
struct mem_fun_invoke; // details, exposes `R operator()(Args...)const`
template<class T, class D, class R, class...Args>
mem_fun_invoke<T,R,Args...>
operator->*(std::shared_ptr<Ts...> const& p, R(T::*Method)(Args...)){
  return mem_fun_invoke<T,R,Args...>(p.get(), Method);
}

使用宏代码(例如在boost中)或样板代码复制来模拟...是一种方法。这种方法可能并不完美(每个参数都需要复制两次?我猜我们可以用T const&参数替换T参数来解决这个问题),但是实现难度很大。
相比之下,使用C++11要容易得多。但是,std shared ptr是与C++11一起设计的,它的前身是在C++11之前设计的。因此,对于这些前身来说,添加->*将需要大量的麻烦和样板文件,而基于这些前身编写了C++11 shared ptr。
然而,这只是一种观点或故事。

一个使用示例不会有坏处 :) - Lightness Races in Orbit
@light heck,我也可以免费提供:只需有一个is_smart_ptr特性。不确定使用示例会包括什么?将其注入到智能指针类中,然后执行(smart_ptr->*method_ptr)(arg1, arg2) - Yakk - Adam Nevraumont
为了帮助提问者,也许值得展示一下这个过程!具体来说,你需要将它注入到一个类中,这意味着它不能与问题所涉及的std::shared_ptr一起使用。 - Lightness Races in Orbit
@light illuminated,还有一个不需要遵守std的版本,因为为什么不呢。仍然没有示例,因为那需要大于4英寸的屏幕,或者麻烦在手机上安装编译器。嗯,我应该这样做。 - Yakk - Adam Nevraumont
我刚意识到你没有回答这个问题。 - Lightness Races in Orbit
@LightnessRacesinOrbit提出了一个关于为什么会被省略的合理的故事。 - Yakk - Adam Nevraumont

2
如果你想要的话,你可以自己添加它。operator ->*是一个普通二元运算符,就像operator +一样,它不需要成为一个成员。
这里有一个稍微更加通用的版本,它将为任何实现operator ->的东西添加->*支持。
template< typename obj, typename ptm >
auto operator ->* ( obj && o, ptm && p )
-> decltype( o.operator -> () ->* p )
    { return o.operator -> () ->* p; }

一个推断的返回类型并不合适,因为它不能提供SFINAE,然后模板可能会干扰其他重载。是的,如果使用forward rvalues,上面的实现会更好。最后,operator ->的下钻行为无法在没有使用递归模板的情况下捕获。

至于为什么shared_ptr没有提供本地支持,指向成员的指针经常被遗忘。标准库中没有任何其他部分有operator ->*,尽管对于迭代器等始终是合适的。这种功能也不在先前的标准库功能之前的Boost.SmartPtr库中。(Boost通常具有更多的功能。)

以上代码会递归调用 ->* 吗?这样会重复钻取吗?还是它不会将自身考虑在 ->* 调用中? - Yakk - Adam Nevraumont
@Yakk 当时我以为不会,但现在我不知道为什么不行。最好尝试一下看看。 - Potatoswatter

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