重载成员访问运算符 ->;, .*

Overloading member access operators ->, .* (C++)

我了解大多数运算符重载,除了成员访问运算符->.*->*等。

尤其是,传递给这些运算符函数的是什么,应该返回什么?

操作员如何工作(如operator->(...)知道被引用的成员?它能知道吗?它还需要知道吗?

最后,是否需要考虑任何常量因素?例如,当重载诸如operator[]之类的内容时,通常需要const和non-const版本。成员访问运算符是否需要常量和非常量版本?


->

这是唯一一个非常棘手的问题。它必须是非静态成员函数,并且不接受参数。返回值用于执行成员查找。

如果返回值是类类型的另一个对象,而不是指针,那么随后的成员查找也由operator->函数处理。这被称为"向下钻取行为"。语言将operator->调用链接在一起,直到最后一个调用返回指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct client
    { int a; };

struct proxy {
    client *target;
    client *operator->() const
        { return target; }
};

struct proxy2 {
    proxy *target;
    proxy &operator->() const
        { return * target; }
};

void f() {
    client x = { 3 };
    proxy y = { & x };
    proxy2 z = { & y };

    std::cout << x.a << y->a << z->a; // print"333"
}

->*

这件事很棘手,因为它没有什么特别之处。非重载版本需要左侧的指向类类型的指针对象和右侧的指向成员类型的指针对象。但是,当您超载它时,您可以接受任何您喜欢的参数,并返回任何您想要的。它甚至不必是非静态成员。

换句话说,这只是一个普通的二元运算符,如+-/。另请参见:free operator->*overloads evil?

.*.

这些不能超载。当左侧是类类型时,已经有了内置的含义。也许能够为左侧的指针定义它们是有点道理的,但是语言设计委员会认为这会比有用更令人困惑。

重载->->*..*只能在表达式未定义的情况下填充,它永远不能更改在不重载的情况下有效的表达式的含义。


operator->是特殊的。

它有附加的、非典型的约束:它必须返回一个对象(或对对象的引用),该对象还具有指针取消引用运算符,或者它必须返回一个指针,该指针可用于选择指针取消引用运算符箭头指向的对象。布鲁斯·埃克尔:思考CPP第一卷:操作员->

额外的功能是为方便起见而提供的,因此您不必打电话

1
a->->func();

您只需执行以下操作:

1
a->func();

这使得operator->不同于其他运算符重载。


您不能重载成员访问.(即->所做的第二部分)。但是,您可以重载一元取消引用运算符*(即->所做的第一部分)。

C++ EDCOX1的0×操作符基本上是两个步骤的结合,如果你认为EDCOX1〔6〕相当于EDCOX1〔7〕,这是很清楚的。当EDCOX1(9)是你的类的一个实例时,C++允许你自定义如何处理EDCOX1的8个部分。

EDOCX1·0超载的语义有些奇怪,因为C++允许您返回一个正则指针(它将被用来找到指向的对象),或者返回另一个类的实例,如果该类还提供了EDCOX1×0的操作符。在第二种情况下,从这个新实例继续搜索未引用的对象。


->操作符不知道指向哪个成员,它只提供一个对象来执行实际的成员访问。

另外,我看不出为什么不能提供const和non-const版本。


当您重载操作符->()(此处不传递任何参数)时,编译器实际执行的操作是递归地调用->直到它返回指向某个类型的实际指针。然后使用正确的成员/方法。

例如,这对于创建封装实际指针的智能指针类很有用。调用重载的operator->执行它的任何操作(例如,为线程安全而锁定),返回内部指针,然后编译器调用->来获取此内部指针。

至于Constness——它已经在评论和其他答案中得到了回答(你可以,也应该,两者都提供)。