【问题标题】:Why can operator-> be overloaded manually?为什么 operator-> 可以手动重载?
【发布时间】:2010-05-30 12:11:10
【问题描述】:

如果p->m 只是(*p).m 的语法糖,那不是很有意义吗?从本质上讲,我写过的每个operator-> 都可以按如下方式实现:

Foo::Foo* operator->()
{
    return &**this;
}

在任何情况下,我希望 p->m 表示 (*p).m 以外的其他含义吗?

【问题讨论】:

  • 对于存在这种递归的类,x->y 被解释为 (x.operator->())->y 似乎很自然。也许他们认为允许递归没有害处,因此没有禁止它?至少我在 ARM 中找不到一些解释。

标签: c++ pointers operator-overloading smart-pointers


【解决方案1】:

operator->() 有一个奇怪的区别,即被隐式调用重复,而返回类型允许它。显示这一点的最清晰方法是使用代码:

struct X {
    int foo;
};

struct Y {
    X x;
    X* operator->() { return &x; }
};

struct Z {
    Y y;
    Y& operator->() { return y; }
};

Z z;
z->foo = 42;          // Works!  Calls both!

我记得有一次这种行为对于使一个对象在类似智能指针的上下文中充当另一个对象的代理是必要的,但我不记得细节了。我记得的是,通过使用这种奇怪的特殊情况,我只能使用a->b 语法使行为按我的意图工作;我找不到让(*a).b 以类似方式工作的方法。

不确定这是否能回答您的问题;真的,我是在说,“好问题,但比这更奇怪!”

【讨论】:

  • +1 但是即使operator-> 被硬连线到语言中,该语言仍然可以强制执行这个奇怪的规则,对吧?从技术上讲,它会比语法糖略多——语法糖浆也许?
  • 您的意思是如果语言规范要求编译器将a->b 重写为(*a).b,那么递归调用operator*() 是否仍然有意义?不,因为有时需要在“到达终点”之前停止(即返回一个类似指针/指针的对象,例如,以便您可以修改它,而不是返回最终的指向对象)。 operator->() 递归怪异是可能的(必要的?)因为它不是真正的“正确的”2-arg 运算符——第二个“参数”必须是 struct 字段名称,这是无法表达的C++ 类型系统。
  • P.S:我会称之为 syntactic molasses - 让我的理解变得缓慢......:P
【解决方案2】:

一个用例可能是当您在 C++ 中创建 DSL 时,按照 Boost Karma 的行(尽管它似乎没有在他们的库中重载 ->),您完全改变了传统的含义C++ 运算符。这是否是一个好主意当然还有待商榷。

【讨论】:

  • +1 是因为这个原因,但这对我来说是个糟糕的主意......就像编写一个改变世界状态的复制 ctor 一样糟糕(因为有时编译器可以选择打电话或不打电话)。
【解决方案3】:

常规指针提供p->m(*p).m,其中p->m 更为常见。如果您只允许重载其中一个,那么它将与默认类型不一致。至于为什么不让编译器重写,简单的回答是因为有时候,你不希望operator->返回T*,而operator*返回T&。允许它们分别重载允许您不一定能想到的组合。但是拒绝它是愚蠢的,仅仅因为我们目前想不出任何改变它的理由。就像,如果你愿意,你可以有一个 int128 类和重载 operator+= 来表示取幂。

【讨论】:

  • 我同意仅仅因为你想不出一个好的理由来禁止某事是不好的想法,所以+1,但是我认为任何支持->重载的论点还将支持. 的重载,C++ 不允许您重载。
  • 不一定。请记住,至少有一个运算符必须保持不可重载,以便人们始终可以访问基类。
  • 我不理解抱歉——为什么能够始终访问基类很重要?此外,我不清楚为什么->. 更适合重载。
  • j_random_hacker:假设 x 类重载了两个且只有两个运算符,. 运算符和 -> 运算符。 . 运算符返回 y 类,-> 运算符返回 z 类,假设 y 类重载运算符 ->。如果我们调用x->member,那么编译器会将其转换为x.operator -> ()->member,但是x 重载了返回y 的运算符.,那么我们调用的是哪个运算符->?正是这种歧义阻止了我们重载运算符.,类似的歧义可能发生在其他运算符上,但运算符. 更容易发生。
  • @Joe:是的,我认为问题在于.-> 都不是真正的“操作员”。所有其他运算符采用 1、2 或 3 个具有可在 C++ 中描述的类型的操作数,而 .-> 都采用一个对象操作数和一个必须是“结构字段名称”的操作数。这两个“运算符”中的至少一个必须不可重载才能使任何成员访问成为可能,我可以看到-> 可以根据. 实现,而反之则不然,所以我明白为什么@ 987654345@ 被选为不可重载的。
【解决方案4】:

你可能想做一些其他的操作,比如增加一个变量(访问计数)甚至一些安全检查等等。另一方面,我从来不需要重载操作符-> ...

【讨论】:

    【解决方案5】:

    我认为它用于 boost 中的 shared_ptr (例如)。这使它们“看起来”像普通指针,尽管它们是特殊指针(引用计数 afaik)。

    【讨论】:

    • 恐怕这并不能告诉我任何有趣的事情。为什么shared_ptr需要重载operator->(),而不是简单地重载operator*(),让编译器将p->m重写为(*p).m?这具有一致性的明显优势——您可以使用任何一种语法并获得相同的结果,这肯定是每个人所期望的。那么为什么不这样做呢?
    猜你喜欢
    • 2018-04-06
    • 2014-02-06
    • 2021-08-23
    • 2011-08-30
    • 2015-03-08
    • 2013-06-12
    • 1970-01-01
    • 2015-06-01
    相关资源
    最近更新 更多