为什么不能在C++中重载'.'运算符?

82

能够在C++中重载 . 运算符并返回对象的引用将非常有用。

可以重载 operator->operator*,但不能重载 operator.

这是由于技术原因吗?


4
您能举个例子说明何时想要重载 '.' 运算符吗? - Toon Krijthe
6
通常情况下,使用案例是“智能引用”,一种代理。 - ddaa
2
@Gamecatпјҡйҳ…иҜ»жӯӨжҸҗжЎҲпјҲhttp://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1671.pdfпјүпјҢд»Ҙж·»еҠ йҮҚиҪҪoperator.е’Ңoperator.*зҡ„иғҪеҠӣпјҢе…¶дёӯеҢ…еҗ«дёҖдәӣзӨәдҫӢгҖӮ - Mankarse
1
@ToonKrijthe 点号周围的空格是允许的,因此可能有一些聪明但令人震惊的动态调度技巧,可以将点积表示为“matrix1.matrix2”。 - mwcz
5
“Operator Dot”,是由Stroustrup和Dos Reis提出的建议。 - emlai
显示剩余4条评论
4个回答

63

请参考Bjarne Stroustrup的这句话:

运算符"."理论上可以使用与"->"相同的技术进行重载。然而,这样做可能会引发一个问题,即操作是否针对重载"."的对象,还是"."所指代的对象。例如:

class Y {
public:
    void f();
    // ...
};

class X {    // assume that you can overload .
    Y* p;
    Y& operator.() { return *p; }
    void f();
    // ...
};

void g(X& x)
{
    x.f();    // X::f or Y::f or error?
}

这个问题可以用几种方法解决。在标准化时,没有明显的最佳选择。有关更多细节,请参阅C++设计和进化


16
我很想将这个投票打下去,因为它涉嫌抄袭/改写。引用时,请逐字引用,不要进行修改,并使用引用格式。 - Sebastian Mach
2
这个例子只是重载决议的歧义,指出需要更加仔细的编程(参见:https://dev59.com/q2vXa4cB1Zd3GeqPHkqC)。这种情况不应成为不重载 operator . 的理由。 - slashmais
@slashmais 不行。operator. 的正当性是与 operator-> 显式并行的。你怎么能进行重载决议呢? - curiousguy
只是要注意的是,后来Bjarne Stroustrup支持_operator dot_,甚至推动了一个提案,但显然尚未被接受:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4477.pdf - 正如@emlai在评论中已经添加到问题中的。 - Amir Kirsh
这是提案的较新版本:http://open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0416r1.pdf - Some Guy
显示剩余2条评论

52

Stroustrup认为C++应该是一种可扩展但不可变的语言。

点操作符(属性访问)被视为过于接近语言核心,不允许重载。

参见《C++的设计与演化》第242页,第11.5.2智能引用节。

当我决定允许重载运算符->时,我自然而然地考虑了是否可以类似地重载运算符.

当时,我认为以下论点是有说服力的:如果obj是一个类对象,则obj.m对于该对象类的每个成员m都有意义。我们尽量避免通过重新定义内置操作使语言变得可变(虽然出于迫切需要,这个规则在=和一元&时被违反)。

如果我们允许对类X重载.,我们将无法通过正常手段访问X的成员;我们将必须使用指针和->,但->&也可能已被重新定义。我想要的是一种可扩展的语言,而不是一种可变的语言。

这些论点是有分量的,但并非定论。尤其是在1990年,Jim Adcock提议允许完全像重载运算符->那样重载运算符.

这个引文中的“我”指的是Bjarne Stroustrup本人。没有比这更权威的了。

如果你真的想了解C++(比如“为什么会变成这样”),你应该绝对阅读这本书。


1
正如其他评论所指出的那样,Stroustrup显然已经改变了他对这个问题的看法,并在2016年提出了允许重载运算符“.”的建议。请参阅https://isocpp.org/blog/2016/02/a-bit-of-background-for-the-operator-dot-proposal-bjarne-stroustrup,了解他为什么这样做的一些评论。 - Some Guy

28

Stroustrup对这个问题有一个回答:

点运算符 . 原则上可以使用与 -> 相同的技术进行重载。然而,这样做可能会引起疑问,即操作是针对重载 . 的对象还是 . 所指代的对象。例如:

class Y {
public:
    void f();
    // ...
};
class X {   // assume that you can overload .
    Y* p;
    Y& operator.() { return *p; }
    void f();
    // ...
};
void g(X& x)
{
    x.f();  // X::f or Y::f or error?
}

这个问题可以用几种方法来解决。在标准化时,哪种方法最好并不明显。有关更多详细信息,请参见D&E


1
请看我对安东回答的评论。 - slashmais
请注意,Stroustrup最近提出了允许重载运算符“.”的建议:http://open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0416r1.pdf - Some Guy

1
理解起来非常容易,只要深入了解操作符函数调用的内部机制即可。
以一个名为complex的类为例,该类有两个成员变量r表示实部,i表示虚部。
假设现在已经定义了一个带两个参数的构造函数,如: Complex C1(10,20),C2(10,2)
当你使用 C1+C2 这样的语句时,编译器会试图查找复数类型上重载的+运算符版本。假设我们已经对+运算符进行了重载,则 C1+C2 会被转化为 c1.operator+(c2) 来执行。
现在假设我们想要重载'.'运算符。如果你尝试像这样调用 C1.disp() 来展示复数对象的内容,那么其内部实现将变得非常混乱,比如: C1.operator.(------)。正是因为这种混乱的实现方式,我们不能重载'.'运算符。

1
有些人说内部翻译不应该调用重载的 operator. - curiousguy
参见C++提案,了解如何使用并不那么混乱的方式:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4477.pdf - Amir Kirsh
这是提案的较新版本:http://open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0416r1.pdf - Some Guy
这里有一个解决许多相同问题的竞争性方案,它尝试使用一种特殊形式的继承来代替重载点运算符:https://open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0352r1.pdf - Some Guy

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