为什么不允许指针的运算符重载工作?

18
根据此答案下的评论所说,引用主要是为了支持操作符重载,Bjarne Stroustrup 的话是这样的:

引用主要是为了支持操作符重载。C通过按值传递每个函数参数,当按值传递对象将会低效或不合适时,用户可以传递一个指针。然而,在使用操作符重载时,这种策略行不通。在这种情况下,符号方便性至关重要,因此如果对象太大,就不能期望用户插入地址运算符。

这意味着指针无法与操作符重载一起使用。但它没有清楚地解释为什么指针无法与操作符重载一起使用。为什么指针不能进行操作符重载?
我认为在使用引用的情况下,也可以使用指针来代替。
9个回答

9
因为如果允许这样做,看起来不太好,也不如使用参考更直观。
假设允许这样做,那么您会写成:
struct A{};
A a, *pa, b;

a = pa ;//doesn't look good, also not intuitive. (not real C++)

这样做看起来不太好,因为左侧是非指针,右侧是指针。看起来非常奇怪。而且,由于类型不匹配,它的确不太直观,到底在做什么。我的意思是,您正在将指针分配给非指针;这种分配应该做什么?将指针所指向的地址的内容复制到目标(非指针)中并不是很直观。

另一方面,由于引用是允许的(这是现实,而不是假设):

a = b; //looks good, intuitive, as now both side is same type

参考文献中提到,当你有两个相同类型的对象时,只有在将b作为参数传递给operator=()时,它才会被按引用(或者说是按指针,因为引用是指针的语法糖)传递以避免不必要的复制。这反过来不会影响性能,因为如果按值传递,则会影响性能。

值得注意的是,不仅b按引用(或者说是底层的指针)传递,a也被传递到函数中,通过指针,因为我们知道在函数中,关键字this实际上是一个指针

因此,引用在C++中被引入,使整个过程对程序员看起来更加美观和直观,否则它们在底层是指针。事实上,大多数编译器在内部使用指针机制来实现引用。


似乎会调用operator=(A*, A**)。我认为这与今天的operator=(A&, A*&)一样罕见。 - Ben Voigt
@Ben:我不明白你想要表达什么。 - Nawaz
1
@Nawaz,我完全理解你的答案。@Ben Voigt,虽然我认为你的答案也是正确的,但我还没有理解它... - cpuer
1
@Nawaz,啊哈,所以我的结论是正确的:引用只是使用指针的一种更优美的方式 :) - cpuer
@cpuer:没错,我的朋友。引用就是永久性的解引用指针。 - Nawaz
显示剩余2条评论

5
为什么指针无法正常工作?因为它具有歧义。是否应该使用指针所指向的内存地址还是使用指针本身的值作为参数,这将导致混淆。
ostream* operator<<(ostream* s, const char* c);

匹配

cout << 'a';

或者

cout << "a";

此外,您不能在临时对象上使用取地址符号(&)。以下代码应该如何处理:

complex<double> a, b, c;
cout << a + b * c;

因为b * c是临时的,而和也是。


@Ben Voigt,我认为它会匹配两者。 - cpuer
它不应该自动获取指针,它不会匹配 cout << 'a'; 但是它会匹配 cout << &'a';,除非编译器抛出左值问题。在这种情况下,您可以使用 char a = 'a'; cout << &a; - Daniel
@cpuer:那么操作员如何知道是打印一个字符还是打印到第一个NUL元素? - Ben Voigt
@Dani: 那么你如何进行指针算术运算呢?complex<double> a[4]; cout << a + 1;会打印出第一个元素加一的和,还是第二个元素呢? - Ben Voigt
@Ben Voigt,换句话说,是因为C指针语法的歧义性导致引入了引用吗? - cpuer
显示剩余2条评论

1

Consider the statement

c= a+b         

其中+运算符已被重载。编译器可以将此语句解释为

c=operator+ (a, b)

由于这里没有传递变量的地址,所以我们无法将它们收集到指针变量中。

如果我们想要将它们收集到指针变量中,那么我们必须修改语句如下:

c=&a+&b;

但这意味着我们试图添加a和b的地址而不是它们的值。在这种情况下,只有引用可以用来节省内存。

1

如果为一个重型类重载加号运算符,你将不得不写 a + b(按值传递,效率低下)或者 &a + &b(丑陋而复杂)来添加两个该类的对象。但是使用引用,即使写 a + b,你也会得到按引用传递。


@cpuer:是的,但a+b&a+&b1更直观。 - In silico
1
@In silico,所以引用只是更加优美的指针使用方式? - cpuer
@cpuer:不要把引用看作指针(或其变体),因为它们不能进行指针算术运算,你不能“重新设置”引用,并且取引用的地址会得到它所引用对象的地址(而不是引用本身)。更有用的是将引用视为所引用对象的别名(编译器可能通过指针实现,但这是一种实现细节)。对别名所做的任何更改都会更改原始对象。 - In silico
@In silico:每次我听到引用被描述为别名(或“变量的另一个名称”)时,人们总是会混淆生命周期的问题。最好将引用视为带有指针解引用(*)永久附加在前面的指针。 - Ben Voigt
@In silico:临时变量的寿命延长不能通过任何方法来解释...必须将其作为引用的特殊特性学习。我担心的是parashift FAQ中可怕的解释。如果引用是对象,那么当引用超出范围时析构函数将被调用。但是绑定到非临时对象的引用不会销毁该对象。引用被绑定,它本身不是对象的名称。 - Ben Voigt
显示剩余5条评论

1

因为大多数运算符在应用于指针时已经有了替代的已知含义。


好的,编译器可以传递每个操作数的地址。 - Ben Voigt
@Ben Voigt:那么它如何能区分两个函数,一个接受指针,另一个接受值? - Benjamin Lindley
我在我的答案中已经强调过的一个问题。如果编译器始终获取每个操作数的地址,那么这是可行的,这样当操作数是指针时,实现将获得双重指针。但这肯定会令人困惑。 - Ben Voigt
@Ben Voigt:但是当你说编译器可以传递每个操作数的地址时,你自己引入了歧义的问题。解决这个问题的方法不是在第一次引入它时就像常规函数参数一样让操作数行事。然后这就留下了我指出的问题。 - Benjamin Lindley
双指针与单指针不会产生歧义。但是,大多数新手程序员甚至不如引用理解双指针(而引用也不如指针)。尽管在另一个没有引用的C++宇宙中可能会有所不同。 - Ben Voigt

1

运算符重载适用于对象,但指针本身不是一个对象。它指向一个对象。


1
循环论证,而且并不完全正确。即使在当前规则下,只需要一个操作数是用户定义类型。 - Ben Voigt
1
此外,根据语言标准,指针一个对象,而引用则不是。 :) - Xeo

0
这并不意味着操作符重载不能与指针一起使用。
它表明提供引用是一种必要的符号便利,以使程序员不必频繁使用取地址运算符。
同时,操作符重载无法与指针一起使用,但这基本上是因为没有语法支持。为什么?请问Stroustrup。
在使用指针之前加上解引用操作符时,您的IMO是正确的。

0

当涉及到内置类型(如intfloat或任何指针类型)时,无法重载运算符。您可以为一个类和一个原始类型重载运算符(或者您可以使用类型转换)。例如,您可以添加std::stringconst char *


循环论证。你不能以当前的规则作为当前规则的理由。你需要一个合理的解释。 - Ben Voigt
原因非常简单。如果可以重载内置运算符(例如自定义 int operator+(int, int)),那么你最终会得到不一致的行为。你的代码将使用你的运算符,而所有其他代码(库)将使用其他(内置)运算符。 - John

0

现在已经是2019年了,但你仍然无法访问本地类型的类。也没有办法覆盖它们上面的运算符。

指针(如char、int、float等)是一种本地类型。你可以创建一个像指针一样行为的对象并重载[->,*]运算符,但这不会重载本地指针上的[->,*]。例如,你还可以创建一个像int一样行为的对象并重载[+,-,*,/],但这不会重载本地int上的[+,-,*,/]。

出于实用和理性的考虑,想象一下如果

this->member

具有用户定义的含义。

参考文献是一个无关的话题,我个人认为。


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