简体   繁体   English

操作符重载是否可以在没有引用的情

[英]Could operator overloading have worked without references?

According to Bjarne Stroustrup, references were introduced into C++ to support operator overloading: 根据Bjarne Stroustrup的说法,引用了C ++以支持运算符重载:

References were introduced primarily to support operator overloading. 引用的引用主要是为了支持运算符重载。

C passes every function argument by value, and where passing an object by value would be inefficient or inappropriate the user can pass a pointer. C按值传递每个函数参数,并且按值传递对象的效率低或不合适,用户可以传递指针。 This strategy doesn't work where operator overloading is used. 在使用运算符重载的情况下,此策略不起作用。 In that case, notational convenience is essential because users cannot be expected to insert address-of operators if the objects are large. 在这种情况下,符号方便是必不可少的,因为如果对象很大,则不能期望用户插入地址运算符。 For example: 例如:

 a = b - c; 

is acceptable (that is, conventional) notation, but 是可接受的(即常规的)符号,但是

 a = &b - &c; 

is not. 不是。 Anyway, &b - &c already has a meaning in C, and I didn't want to change that. 无论如何, &b - &c已经在C中有意义,我不想改变它。

Having both pointers and references in the language is a constant source of confusion for C++ novices. 在语言中使用指针和引用是C ++新手混淆的常见原因。

Couldn't Bjarne have solved this problem by introducing a special language rule that allowed object arguments to decay into pointers if a user-defined operator function exists that takes such pointers? Bjarne无法通过引入一种特殊的语言规则来解决这个问题,如果存在需要这样的指针的用户定义的运算符函数,它允许对象参数衰减成指针吗?

The declaration and usage of subtraction would then have looked like: 减法的声明和用法将如下所示:

Foo operator-(const Foo* x, const Foo* y);
a = b - c;

Was such a solution ever proposed/considered? 有没有提出/考虑过这样的解决方案? Would there be any serious downsides to it? 它有任何严重的缺点吗?

Yes I know, references provide other advantages due to their restrictions, but that's not the point. 是的我知道,由于它们的限制,引用提供了其他优点,但这不是重点。

Interestingly, the assignment operator of C with classes seems to have worked exactly like that: 有趣的是,带有类的C的赋值运算符似乎完全相同:

Changing the meaning of assignment for objects of a class [...] is done by declaring a class member function called operator= . 通过声明一个名为operator=的类成员函数来更改类[...]对象的赋值含义。 For example: 例如:

 class x { public: int a; class y * p; void operator = (class x *); }; 

IME, automatic conversion is the bane of C++ . IME,自动转换是C ++的祸根 Let's all write thankful emails to Bjarne Stroustrup for not adding a wide-sweeping automatic conversion from object to pointer. 让我们写一些感谢的电子邮件给Bjarne Stroustrup,因为它没有添加从对象到指针的广泛自动转换。

If you look for a technical reason: In C++, the subtraction of pointers, even of pointers to user-defined types, is already well-defined. 如果你寻找一个技术原因:在C ++中,指针的减法,甚至指向用户定义类型的指针,已经很好地定义了。 Also, this would lead to ambiguities with overloaded versions of operators that take objects per copy. 此外,这会导致每个副本获取对象的重载版操作符的含糊不清。

I don't see how this solves the problem: operator- called on pointers already has a meaning. 我没有看到这是如何解决这个问题的: operator-调用指针已经有了意义。

You'd be defining an operator- for Foo* arguments, but that already exists. 您将为Foo*参数定义一个operator- ,但这已存在。

Then you'd need some contrived semantics that "when called explicitly with pointers, the pointer arithmetic operator is called. When called with object lvalues, they decay into pointers, and then the overloaded version is called". 然后你需要一些人为的语义,“当用指针显式调用时,调用指针算术运算符。当用对象左值调用时,它们会衰减为指针,然后调用重载版本”。 Which, to be honest, seems far more contrived, and much less intuitive than just adding references. 说实话,这似乎更加人为,而且不仅仅是添加引用。

And then inside the operator- , I get the arguments as pointers, and then I'd better make sure to dereference those if I need to call another (or the same) operator from there. 然后在operator-内部,我将参数作为指针,然后如果我需要从那里调用另一个(或相同的)运算符,我最好确保取消引用它们。 Otherwise I'd accidentally end up performing pointer arithmetics. 否则我不小心最终会执行指针算术。

You're proposing an implicit conversion which has different semantics than doing the conversion yourself. 您提出的隐式转换与自己进行转换的语义不同。 That is: 那是:

Foo foo;
bar(foo); 

performs an implicit conversion from Foo to Foo* , and then a function with the signature void bar(Foo*) is called. 执行从FooFoo*的隐式转换,然后调用带有签名void bar(Foo*)函数。

But this code would do something completely different: 但是这段代码会做一些完全不同的事情:

Foo foo;
bar(&foo);

that would also convert to a Foo* , and it would also call a function with the signature void bar(Foo*) , but it would potentially be a different function. 也会转换为Foo* ,它也会调用带有签名void bar(Foo*)函数,但它可能是一个不同的函数。 (In the case of operator- , for example, one would be the user-overloaded operator, and the other would be the standard pointer arithmetic one. (例如,在operator-的情况下,一个是用户重载的运算符,另一个是标准指针算术运算符。

And then consider template code. 然后考虑模板代码。 In general, implicit conversions make templates really painful because the type passed in might not be the type you want to operate on. 通常,隐式转换会使模板非常痛苦,因为传入的类型可能不是您要操作的类型。

Of course, there are more practical problems too: 当然,还有更多的实际问题:

References enable optimizations that aren't possible with pointers. 引用启用指针无法实现的优化。 (The compiler can assume they're never null, and it may be better able to do aliasing analysis) (编译器可以假设它们永远不会为空,并且可能更好地进行别名分析)

Moreover, code would fail silently if no matching operator- can be found. 而且,如果找不到匹配的operator- ,代码将无声地失败。 Instead of telling me that , the compiler would implicitly start doing pointer arithmetics. 而是告诉的,编译器会隐式地开始做指针算术。 Not a deal-breaker, but I really really prefer errors to be caught early where possible. 不是一个交易破坏者,但我真的更喜欢在可能的情况下及早发现错误。

And one of the goals with C++ was to make it generic: to avoid having "magic" types or functions. C ++的目标之一是使其具有通用性:避免使用“魔术”类型或功能。 In real-world C++, operators are very much just functions with a different syntax. 在现实世界的C ++中,运算符只是具有不同语法的函数。 With this rule, they would have different semantics as well. 有了这个规则,它们也会有不同的语义。 (What if the operator is called with function syntax? operator+(a, b) ? (如果使用函数语法调用运算符怎么operator+(a, b)operator+(a, b)

C++ aims at being strongly typed, so trying to be consistent with that philosophy, it makes sense that an object is-not-a pointer to that object, and I totally agree with sbi about how great it is not to have automatic conversion happening all around (I have in mind multi-contributors projects). C ++的目标是强类型,所以试图与这种哲学保持一致,有意义的是一个对象不是指向该对象指针,我完全同意sbi关于不能自动转换发生的重要性。周围(我想到了多个贡献者项目)。

To address your concern more specifically, people learning C++ can get confused at first by references vs. pointer, but I am not sure it would clarify anything for them to have this kind of automatic conversion happening on their behalf. 为了更具体地解决您的问题,学习C ++的人最初可能会被引用与指针混淆,但我不确定它会为他们澄清任何代表他们进行这种自动转换的内容。
For example : 例如 :

Foo ** operator+(Foo **lhs, Foo **rhs)
{...}

Foo *varFoo1,*varFoo2;
varFoo1 + &varFoo2;

Following the hypothetical implicit object-to-pointer, should varFoo1 be accepted as an argument to a method expecting Foo ** ? 在假设的隐式对象到指针之后,是否应该接受varFoo1作为期望Foo **的方法的参数? Because the method expects a pointer to Foo * and varFoo1 *is-a* Foo * . 因为该方法需要指向Foo *varFoo1 *的指针是-a * Foo *

An other advantage of reference used as arguments : 用作参数的引用的另一个优点:
const references can receive a rvalue as argument (ex. the classic string literal), while pointers can't. const引用可以接收rvalue作为参数 (例如经典字符串文字),而指针则不能。

If you accept that doing it with pointers (either implicitly or explicitly) would introduce really confusing semantics (am I operating on the pointer or the pointee?) then without references that only leaves call by value. 如果您接受使用指针(隐式或显式)执行它会引入真正令人困惑的语义(我操作指针或指针对象吗?)然后没有引用只留下值调用。 (Besides your Foo c = &b - &a; example consider the case where you wanted to write an operator that really did use a pointer as one of it's arguments) (除了你的Foo c = &b - &a;例子考虑你想编写一个真正使用指针作为其参数之一的运算符的情况)

I don't think pointers without & at the call site is usefully-workable and certainly no better than references. 我不认为没有&在呼叫站点的指针是有用的 - 可行的,当然也不比参考更好。 If you make it a special feature of operator s and only operators then that moves the behaviour well into the "cryptic" special case realm. 如果你把它作为operator的一个特殊功能,那么只有运算符才能将行为很好地转移到“神秘”的特殊情况领域。 If you want to reduce the "special case" aspect of it by making it a general feature then I don't think it's helpful or useful over the call by reference as it stands. 如果你想通过使它成为一般功能来减少它的“特殊情况”方面,那么我认为它对于通过引用的调用没有帮助或有用。

For example if I want to write an operator that takes a Foo and a void* I can write: 例如,如果我想编写一个带有Foovoid*的运算符,我可以写:

Foo operator+(const Foo& f, void *ptr);

Under your proposed rules that would become: Foo operator+(Foo *f, void *ptr); 根据您提出的规则: Foo operator+(Foo *f, void *ptr); . The problem as I see it would then be that even though I wanted ptr to explicitly be a pointer by the "implict & rule" it would accept anything and there doesn't seem to be a way for me to disallow the automatic conversion. 我认为问题就在于,即使我希望ptr明确地成为“implict & rule”的指针,它也会接受任何东西 ,似乎没有办法让我不允许自动转换。 So double d; Foo f; f = f + d; 所以double d; Foo f; f = f + d; double d; Foo f; f = f + d; would match that, as would int i; Foo f; f = f + i; 会匹配,就像int i; Foo f; f = f + i; int i; Foo f; f = f + i; , potentially in two different ways! ,可能有两种不同的方式!

Call by value might have worked and made sense for "simple" types, and you could use smart pointers for the cases where you really don't want to have to take a copy of each operand. 按值调用可能对“简单”类型有效并且有意义,并且您可以使用智能指针来处理您真正不希望获取每个操作数的副本的情况。 In general though the idea of being forced to copy everything seems very unclean compared to a reference based approach. 总的来说,与基于参考的方法相比,被迫复制一切的想法似乎非常不洁净。

I've never read the D&E book, but my understanding is that references weren't added to make it look better when passing arguments to a function, but to make it look better when yielding values from them. 我从来没有读过D&E书,但我的理解是,在将参数传递给函数时,没有添加引用以使其看起来更好,而是在从它们产生值时使其看起来更好。 Both simple and compound assignment and subscript operators all result in lvalues when used on built-in types, but there is no way to do the same with built in types without references. 简单和复合赋值和下标运算符在内置类型上使用时都会产生左值,但是没有方法可以在没有引用的情况下使用内置类型。

I also wonder how you would go about implementing an operator that operates on both a type and a pointer to a type. 我也想知道如何实现一个对类型和指针类型进行操作的运算符。

struct Foo { };
struct Bar { };
Foo operator +(Foo&, Bar*);
Bar operator +(Foo*, Bar&);
Foo operator +(Bar*, Foo&);
Bar operator +(Bar&, Foo*);

versus

struct Foo { };
struct Bar { };
Foo operator +(Foo*, Bar*);
Bar operator +(Foo*, Bar*);
Foo operator +(Bar*, Foo*);
Bar operator +(Bar*, Foo*);

The same thing could be shown with just one type, but simply saying "don't do that" seems reasonable in that case... when multiple types are involved, the chances of accidentally introducing an ambiguity increases. 只有一种类型可以显示同样的事情,但在这种情况下简单地说“不要那样做”似乎是合理的......当涉及多种类型时,意外引入歧义的可能性增加。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM