搜索

首页  >  问答  >  正文

重载操作符 - C++重载'->'符号是怎么实现的

例如下面的代码:

class StrPtr{
public:
    StrPtr() : _ptr(nullptr){}
    //拷贝构造函数等省略...
    std::string* operator->()
    {
        return _ptr;
    }
private:
    std::string *_ptr;
};
std::string* operator->()
/*
这句代码的格式不是类似于前导运算符吗?类似于std::string operator*(){//...}不正是*ptr?但是重载的->运算符却是ptr->这样使用的,请问是为什么?
而且std::string* operator->()返回的是指针,为什么可以直接在后面访问类成员,[比如说ptr->size()]?
我的疑问是这是如何实现的,这和我对运算符重载的直接理解有所差异。
*/
PHPzPHPz2804 天前754

全部回复(3)我来回复

  • PHP中文网

    PHP中文网2017-04-17 13:33:21

    这句代码的格式不是类似于前导运算符吗?类似于std::string operator*(){//...}不正是*ptr?但是重载的->运算符却是ptr->这样使用的,请问是为什么?

    因为运算符的结合律不同,dereference operator(*)是 right associative 的,而 member access operator(.->)是 left associative 的。这是不同符号的结合律不同的例子,C++ 里符号相同的时候也会有用法不同导致结合律不同的例子。例如:

     operator | left associative            | right associative
              |   lhs op rhs  /  lhs op     |   op rhs
    ----------+-----------------------------+---------------------------
     ++ --    | postfix increment/decrement | prefix increment/decrement
     + -      | binary add/subtract         | unary plus/minus
     *        | binary multiply             | dereference
     &        | bitwise and                 | address-of
     ()       | function call               | type conversion

    当你重载一个左结合律的操作符时(如+ - * / ()等),往往这个操作符是个二元操作符,对于lhs op rhs,就会调用lhs.operator op(rhs)operator op(lhs, rhs),我们只需要按照这个函数签名来重载操作符就行了。

    但是,如果这个操作符是一元操作符怎么办?这就要分情况讨论了:

    • 如果这个符号有一种以上的用法(比如++--),对于lhs op(左结合)和op rhs(右结合),我们得想个办法区分不同的用法啊,所以就会出现用于占位的函数参数(int):

      • 对于lhs op调用lhs.operator op(int)

      • 对于op rhs调用rhs.operator op()

    • 如果这个符号只有一种用法(比如->),对于lhs op,那就不需要用于占位的函数参数了,可以直接写成类似右结合的函数签名,即

      • 对于lhs op调用lhs.operator op()

    所以会让人有点糊涂为啥函数签名差不多,用法却不同。

    而且std::string* operator->()返回的是指针,为什么可以直接在后面访问类成员,[比如说ptr->size()]?

    这是由 C++ 标准规定的,对于ptr->mem根据ptr类型的不同,操作符->的解释也不同:

    • ptr的类型是内置指针类型时,等价于(*ptr).mem

    • ptr的类型是类时,等价于ptr.operator->()->mem

    你会发现这是一个递归的解释,对于ptr->mem会递归成:

    (*(ptr.operator->().operator->().….operator->())).mem

    操作符->是一元的,而操作符.是二元的。操作符->最终是通过操作符.来访问成员的,而.这个操作符是不允许重载的,只能由编译器实现。

    举个例子,使用题主定义的类StrPtr

    string s = "abc";
    StrPtr ptr(&s);
    string *sp = &s;
    
    ptr->size();
    // 等价于 ptr.operator->()->size();
    // 等价于 _ptr->size();   这跟 sp->size();   不就一样了吗
    // 等价于 (*_ptr).size(); 这跟 (*sp).size(); 不就一样了吗

    最后一句题外话,C++ 变量名不要用下划线作为起始,用下划线做起始是保留给编译器使用的。可以使用m_somemembersomemember_作为私有成员名称。

    回复
    0
  • 天蓬老师

    天蓬老师2017-04-17 13:33:21

    标准规定的写法,跟返回值的类型是指针没有什么关系。
    而且神奇的是,如果返回的不是一个指针而是一个重载了箭头操作符的类对象,那么又会调用该类对象的箭头操作符,直到某个箭头操作符返回的是一个raw指针,则对该指针取内容并进行成员访问,这个过程可以无限嵌套,层层递推。如果编译器一层层地找下去,没有找到返回一个raw指针的箭头操作符则会报错,比如返回了一个没有重载箭头操作符的类对象,或者int之类的值。
    如果按照一般的操作符重载逻辑,那么箭头操作符应该返回一个引用,逻辑上好像也没什么问题。但如果出现上述的情况,恐怕就会出现
    -> -> ... ->
    这种东西了。我想这大概是原因之一吧。
    希望和有不同理解的人交流一下。

    回复
    0
  • 怪我咯

    怪我咯2017-04-17 13:33:21

    标准规定,->要么调用返回对象的->,要么返回指针,要么报错。

    回复
    0
  • 取消回复